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