@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.
- package/constants/unified.js +0 -14
- package/package.json +1 -1
- package/services/api.js +0 -17
- package/services/tests/api.test.js +0 -85
- package/utils/commonUtils.js +0 -10
- package/utils/tests/commonUtil.test.js +0 -169
- package/v2Components/CapTagList/index.js +0 -10
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -70
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -207
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +53 -87
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +1 -20
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +34 -145
- package/v2Components/CommonTestAndPreview/actions.js +0 -10
- package/v2Components/CommonTestAndPreview/constants.js +1 -53
- package/v2Components/CommonTestAndPreview/index.js +168 -1006
- package/v2Components/CommonTestAndPreview/messages.js +3 -147
- package/v2Components/CommonTestAndPreview/reducer.js +0 -10
- package/v2Components/CommonTestAndPreview/sagas.js +6 -15
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +286 -328
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +24 -65
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -31
- package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -168
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +0 -71
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +0 -17
- package/v2Components/FormBuilder/index.js +1 -7
- package/v2Components/TestAndPreviewSlidebox/index.js +1 -8
- package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
- package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
- package/v2Containers/CreativesContainer/constants.js +0 -9
- package/v2Containers/CreativesContainer/index.js +93 -286
- package/v2Containers/CreativesContainer/index.scss +1 -51
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +10 -20
- package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
- package/v2Containers/Rcs/constants.js +1 -34
- package/v2Containers/Rcs/index.js +884 -999
- package/v2Containers/Rcs/index.scss +6 -85
- package/v2Containers/Rcs/messages.js +1 -10
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +2453 -41456
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
- package/v2Containers/Rcs/tests/index.test.js +38 -41
- package/v2Containers/Rcs/tests/mockData.js +0 -38
- package/v2Containers/Rcs/tests/utils.test.js +1 -379
- package/v2Containers/Rcs/utils.js +10 -358
- package/v2Containers/Sms/Create/index.js +38 -100
- package/v2Containers/SmsTrai/Create/index.js +4 -9
- package/v2Containers/SmsTrai/Edit/constants.js +0 -2
- package/v2Containers/SmsTrai/Edit/index.js +128 -609
- package/v2Containers/SmsTrai/Edit/messages.js +4 -9
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2600 -4586
- package/v2Containers/SmsWrapper/index.js +8 -37
- package/v2Containers/TagList/index.js +0 -6
- package/v2Containers/Templates/_templates.scss +2 -63
- package/v2Containers/Templates/actions.js +0 -11
- package/v2Containers/Templates/constants.js +0 -2
- package/v2Containers/Templates/index.js +40 -90
- package/v2Containers/Templates/sagas.js +12 -57
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1079 -1043
- package/v2Containers/Templates/tests/sagas.test.js +123 -193
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
- package/v2Containers/TemplatesV2/index.js +23 -86
- package/v2Containers/Whatsapp/index.js +20 -3
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +4872 -5790
- package/utils/templateVarUtils.js +0 -172
- package/utils/tests/templateVarUtils.test.js +0 -160
- package/v2Components/CommonTestAndPreview/AddTestCustomer.js +0 -42
- package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +0 -155
- package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +0 -93
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
- package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +0 -66
- package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -648
- package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +0 -174
- package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +0 -114
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -87
- package/v2Components/SmsFallback/constants.js +0 -73
- package/v2Components/SmsFallback/index.js +0 -955
- package/v2Components/SmsFallback/index.scss +0 -265
- package/v2Components/SmsFallback/messages.js +0 -78
- package/v2Components/SmsFallback/smsFallbackUtils.js +0 -107
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -197
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -261
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
- package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
- package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
- package/v2Components/VarSegmentMessageEditor/index.js +0 -125
- package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -67
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -205
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -251
- package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
- package/v2Containers/SmsTrai/Edit/index.scss +0 -121
- package/v2Containers/Templates/TemplatesActionBar.js +0 -101
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
- 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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
283
|
-
const
|
|
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
|
|
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
|
|
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 = '',
|
|
200
|
+
domainId = '', gsmSenderId = '', senderEmail = '', senderMobNum = '',
|
|
429
201
|
} = testPreviewDeliverySettings[channel] || {};
|
|
430
|
-
const isEmptySelection = !domainId && !gsmSenderId && !
|
|
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
|
|
438
|
-
? domains.filter((domain) => (domain
|
|
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
|
|
455
|
-
: firstDomain
|
|
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
|
-
|
|
510
|
-
), [
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
2236
|
+
requiredTags.forEach((tag) => {
|
|
2794
2237
|
requiredTagObj[tag?.fullPath] = '';
|
|
2795
2238
|
});
|
|
2796
2239
|
if (allTags.length > 0) {
|
|
2797
2240
|
const payload = preparePreviewPayload(
|
|
2798
|
-
|
|
2241
|
+
channel,
|
|
2799
2242
|
formData || {},
|
|
2800
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2271
|
+
channel,
|
|
2829
2272
|
formData || {},
|
|
2830
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
2439
|
+
channel,
|
|
2991
2440
|
formData || {},
|
|
2992
|
-
|
|
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
|
-
|
|
2456
|
+
channel,
|
|
3008
2457
|
formData || {},
|
|
3009
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
|
2530
|
+
const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP].includes(channel)
|
|
3289
2531
|
? testPreviewDeliverySettings[channel]
|
|
3290
2532
|
: null;
|
|
3291
2533
|
|
|
3292
|
-
//
|
|
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
|
|
3346
|
-
extractedTags={
|
|
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
|
|
2597
|
+
isExtractingTags={isExtractingTags}
|
|
3365
2598
|
isUpdatePreviewDisabled={isUpdatePreviewDisabled}
|
|
3366
2599
|
showJSON={showJSON}
|
|
3367
2600
|
setShowJSON={setShowJSON}
|
|
3368
2601
|
customValues={customValues}
|
|
3369
2602
|
handleJSONTextChange={handleJSONTextChange}
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
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
|
-
|
|
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;
|