@capillarytech/creatives-library 8.0.345-alpha.13 → 8.0.345-alpha.15
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/package.json +1 -1
- package/services/api.js +0 -20
- package/services/tests/api.test.js +13 -59
- package/utils/commonUtils.js +19 -1
- package/utils/rcsPayloadUtils.js +92 -0
- package/utils/templateVarUtils.js +201 -0
- package/utils/tests/templateVarUtils.test.js +204 -0
- package/v2Components/CapActionButton/constants.js +7 -0
- package/v2Components/CapActionButton/index.js +167 -109
- package/v2Components/CapActionButton/index.scss +157 -6
- package/v2Components/CapActionButton/messages.js +19 -3
- package/v2Components/CapActionButton/tests/index.test.js +41 -17
- package/v2Components/CapCustomSkeleton/index.js +1 -1
- package/v2Components/CapCustomSkeleton/tests/__snapshots__/index.test.js.snap +12 -12
- package/v2Components/CapTagList/index.js +10 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +160 -15
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +18 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +341 -76
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
- package/v2Components/CommonTestAndPreview/constants.js +38 -2
- package/v2Components/CommonTestAndPreview/index.js +676 -186
- package/v2Components/CommonTestAndPreview/messages.js +49 -3
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/v2Components/CommonTestAndPreview/sagas.js +15 -6
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
- 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 +34 -13
- 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 +132 -4
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
- package/v2Components/FormBuilder/index.js +8 -10
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
- package/v2Components/SmsFallback/constants.js +73 -0
- package/v2Components/SmsFallback/index.js +955 -0
- package/v2Components/SmsFallback/index.scss +265 -0
- package/v2Components/SmsFallback/messages.js +78 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +118 -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 +197 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +277 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +33 -23
- package/v2Components/TemplatePreview/constants.js +2 -0
- package/v2Components/TemplatePreview/index.js +143 -28
- package/v2Components/TemplatePreview/tests/index.test.js +142 -0
- 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/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +11 -4
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
- package/v2Containers/CreativesContainer/constants.js +9 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
- package/v2Containers/CreativesContainer/index.js +300 -108
- package/v2Containers/CreativesContainer/index.scss +51 -1
- package/v2Containers/CreativesContainer/messages.js +0 -4
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +78 -34
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +357 -98
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -18
- 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/Rcs/constants.js +119 -8
- package/v2Containers/Rcs/index.js +2379 -807
- package/v2Containers/Rcs/index.js.rej +1336 -0
- package/v2Containers/Rcs/index.scss +276 -6
- package/v2Containers/Rcs/index.scss.rej +74 -0
- package/v2Containers/Rcs/messages.js +38 -3
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98018 -70073
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +128 -0
- package/v2Containers/Rcs/tests/index.test.js +152 -121
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
- package/v2Containers/Rcs/tests/utils.test.js +646 -30
- package/v2Containers/Rcs/utils.js +478 -11
- package/v2Containers/Sms/Create/index.js +100 -40
- 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 +636 -130
- package/v2Containers/SmsTrai/Edit/index.scss +121 -0
- package/v2Containers/SmsTrai/Edit/messages.js +14 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
- package/v2Containers/SmsWrapper/index.js +37 -8
- package/v2Containers/TagList/index.js +6 -0
- package/v2Containers/Templates/ChannelTypeIllustration.js +6 -23
- package/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/v2Containers/Templates/_templates.scss +181 -126
- package/v2Containers/Templates/actions.js +11 -36
- package/v2Containers/Templates/constants.js +2 -23
- package/v2Containers/Templates/index.js +142 -333
- package/v2Containers/Templates/messages.js +0 -68
- package/v2Containers/Templates/reducer.js +0 -68
- package/v2Containers/Templates/sagas.js +55 -98
- package/v2Containers/Templates/selectors.js +0 -12
- package/v2Containers/Templates/tests/ChannelTypeIllustration.test.js +0 -12
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1042 -1256
- package/v2Containers/Templates/tests/index.test.js +0 -6
- package/v2Containers/Templates/tests/reducer.test.js +0 -178
- package/v2Containers/Templates/tests/sagas.test.js +200 -436
- package/v2Containers/Templates/tests/selector.test.js +0 -32
- 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 +86 -23
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/v2Containers/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
- package/v2Containers/Assets/images/archive_Empty_Illustration.svg +0 -9
|
@@ -31,7 +31,8 @@ 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 { extractTemplateVariables } from '../../utils/templateVarUtils';
|
|
35
36
|
import AddTestCustomerButton from './AddTestCustomer';
|
|
36
37
|
import ExistingCustomerModal from './ExistingCustomerModal';
|
|
37
38
|
// Import constants
|
|
@@ -81,11 +82,23 @@ import {
|
|
|
81
82
|
IMAGE,
|
|
82
83
|
VIDEO,
|
|
83
84
|
URL,
|
|
84
|
-
|
|
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,
|
|
85
93
|
} from './constants';
|
|
86
|
-
|
|
87
|
-
// Import utilities
|
|
88
94
|
import { getCdnUrl } from '../../utils/cdnTransformation';
|
|
95
|
+
import {
|
|
96
|
+
normalizePreviewApiPayload,
|
|
97
|
+
extractPreviewFromLiquidResponse,
|
|
98
|
+
getSmsFallbackTextForTagExtraction,
|
|
99
|
+
} from './previewApiUtils';
|
|
100
|
+
import { pickFirstSmsFallbackTemplateString } from '../../v2Containers/Rcs/rcsLibraryHydrationUtils';
|
|
101
|
+
|
|
89
102
|
import { isValidEmail, isValidMobile, formatPhoneNumber } from '../../utils/commonUtils';
|
|
90
103
|
import { getMembersLookup } from '../../services/api';
|
|
91
104
|
|
|
@@ -108,6 +121,85 @@ const filterUsableGsmSendersForDomain = (domain, gsmSenders, { skipDomainNameEch
|
|
|
108
121
|
});
|
|
109
122
|
};
|
|
110
123
|
|
|
124
|
+
/** Preview payload from Redux may be an Immutable Map — normalize for React state. */
|
|
125
|
+
const toPlainPreviewData = (data) => {
|
|
126
|
+
if (data == null) return null;
|
|
127
|
+
const plain = typeof data.toJS === 'function' ? data.toJS() : data;
|
|
128
|
+
return normalizePreviewApiPayload(plain);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Merge existing customValues with tag keys from categorized groups.
|
|
133
|
+
* Each group is { required, optional } (arrays of tag objects with fullPath).
|
|
134
|
+
* Preserves existing values; adds '' for any tag key not yet present.
|
|
135
|
+
* Reusable for RCS+fallback and any flow that needs to ensure customValues has keys for tags.
|
|
136
|
+
*/
|
|
137
|
+
const mergeCustomValuesWithTagKeys = (prev, ...categorizedGroups) => {
|
|
138
|
+
const next = { ...(prev || {}) };
|
|
139
|
+
categorizedGroups.forEach((group) => {
|
|
140
|
+
[...(group.required || []), ...(group.optional || [])].forEach((tag) => {
|
|
141
|
+
const key = tag?.fullPath;
|
|
142
|
+
if (key && next[key] === undefined) next[key] = '';
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
return next;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** True when `body` contains `{{name}}` mustache tokens (user-fillable personalization tags).
|
|
149
|
+
* DLT `{#name#}` slots are pre-bound template variables and are intentionally excluded. */
|
|
150
|
+
const smsTemplateHasMustacheTags = (body) =>
|
|
151
|
+
typeof body === 'string' && SMS_MUSTACHE_TAG_PATTERN.test(body);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Build tag rows from `{{…}}` mustache tokens only — DLT `{#…#}` slots are excluded because
|
|
155
|
+
* they are pre-bound template variables, not user-fillable personalization tags.
|
|
156
|
+
* Passing a mustache-only captureRegex to extractTemplateVariables skips the DLT branch.
|
|
157
|
+
* A non-global regex is used so ensureGlobalRegexForExecLoop creates a fresh instance on each call.
|
|
158
|
+
*/
|
|
159
|
+
const buildSyntheticSmsMustacheTags = (body = '') => {
|
|
160
|
+
if (!body || typeof body !== 'string') return [];
|
|
161
|
+
return extractTemplateVariables(body, /\{\{([^}]+)\}\}/).map((name) => ({
|
|
162
|
+
name,
|
|
163
|
+
metaData: { userDriven: false },
|
|
164
|
+
children: [],
|
|
165
|
+
}));
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/** RCS createMessageMeta: media shape (mediaUrl, thumbnailUrl, height string). */
|
|
169
|
+
const normalizeRcsTestCardMedia = (media) => {
|
|
170
|
+
if (!media || typeof media !== 'object') return undefined;
|
|
171
|
+
const mediaUrl =
|
|
172
|
+
media.mediaUrl != null && String(media.mediaUrl).trim() !== ''
|
|
173
|
+
? String(media.mediaUrl)
|
|
174
|
+
: media.url != null && String(media.url).trim() !== ''
|
|
175
|
+
? String(media.url)
|
|
176
|
+
: '';
|
|
177
|
+
const thumbnailUrl = media.thumbnailUrl != null ? String(media.thumbnailUrl) : '';
|
|
178
|
+
const height = media.height != null ? String(media.height) : undefined;
|
|
179
|
+
const out = { mediaUrl, thumbnailUrl };
|
|
180
|
+
if (height) out.height = height;
|
|
181
|
+
return out;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/** RCS createMessageMeta: suggestion shape (index, type, text, phoneNumber, url, postback). */
|
|
185
|
+
const mapRcsSuggestionForTestMeta = (suggestionRow, index) => ({
|
|
186
|
+
index,
|
|
187
|
+
type: suggestionRow?.type ?? '',
|
|
188
|
+
text: suggestionRow?.text != null ? String(suggestionRow.text) : '',
|
|
189
|
+
phoneNumber:
|
|
190
|
+
suggestionRow?.phoneNumber != null
|
|
191
|
+
? String(suggestionRow.phoneNumber)
|
|
192
|
+
: suggestionRow?.phone_number != null
|
|
193
|
+
? String(suggestionRow.phone_number)
|
|
194
|
+
: '',
|
|
195
|
+
url: suggestionRow?.url !== undefined ? suggestionRow.url : null,
|
|
196
|
+
postback:
|
|
197
|
+
suggestionRow?.postback != null
|
|
198
|
+
? String(suggestionRow.postback)
|
|
199
|
+
: suggestionRow?.text != null
|
|
200
|
+
? String(suggestionRow.text)
|
|
201
|
+
: '',
|
|
202
|
+
});
|
|
111
203
|
|
|
112
204
|
/**
|
|
113
205
|
* CapTreeSelect and group resolution use strict equality; API data may mix numeric and string ids.
|
|
@@ -133,7 +225,7 @@ const testEntityIdsEqual = (a, b) => {
|
|
|
133
225
|
*/
|
|
134
226
|
const CommonTestAndPreview = (props) => {
|
|
135
227
|
const {
|
|
136
|
-
intl: { formatMessage },
|
|
228
|
+
intl: { formatMessage, locale: userLocale = 'en' },
|
|
137
229
|
show,
|
|
138
230
|
onClose,
|
|
139
231
|
channel, // The channel: 'EMAIL', 'SMS', 'RCS', etc.
|
|
@@ -169,12 +261,21 @@ const CommonTestAndPreview = (props) => {
|
|
|
169
261
|
...additionalProps
|
|
170
262
|
} = props;
|
|
171
263
|
|
|
264
|
+
const smsFallbackContent = additionalProps?.smsFallbackContent;
|
|
265
|
+
const smsFallbackTextForTagExtraction = useMemo(
|
|
266
|
+
() => getSmsFallbackTextForTagExtraction(smsFallbackContent),
|
|
267
|
+
[smsFallbackContent],
|
|
268
|
+
);
|
|
172
269
|
// ============================================
|
|
173
270
|
// STATE MANAGEMENT
|
|
174
271
|
// ============================================
|
|
175
272
|
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
|
176
273
|
const [requiredTags, setRequiredTags] = useState([]);
|
|
177
274
|
const [optionalTags, setOptionalTags] = useState([]);
|
|
275
|
+
const [smsFallbackExtractedTags, setSmsFallbackExtractedTags] = useState([]);
|
|
276
|
+
const [smsFallbackRequiredTags, setSmsFallbackRequiredTags] = useState([]);
|
|
277
|
+
const [smsFallbackOptionalTags, setSmsFallbackOptionalTags] = useState([]);
|
|
278
|
+
const [isExtractingSmsFallbackTags, setIsExtractingSmsFallbackTags] = useState(false);
|
|
178
279
|
const [customValues, setCustomValues] = useState({});
|
|
179
280
|
const [showJSON, setShowJSON] = useState(false);
|
|
180
281
|
const [tagsExtracted, setTagsExtracted] = useState(false);
|
|
@@ -186,6 +287,8 @@ const CommonTestAndPreview = (props) => {
|
|
|
186
287
|
const [customerData, setCustomerData] = useState({ name: '', email: '', mobile: '', customerId: '' });
|
|
187
288
|
|
|
188
289
|
const [previewDevice, setPreviewDevice] = useState(initialDevice);
|
|
290
|
+
const [activePreviewTab, setActivePreviewTab] = useState(PREVIEW_TAB_RCS);
|
|
291
|
+
const [smsFallbackPreviewText, setSmsFallbackPreviewText] = useState(undefined);
|
|
189
292
|
// Track if a preview call has been made (to know when to use previewDataHtml vs raw content)
|
|
190
293
|
const [hasPreviewCallBeenMade, setHasPreviewCallBeenMade] = useState(false);
|
|
191
294
|
const [previewDataHtml, setPreviewDataHtml] = useState(() => {
|
|
@@ -218,15 +321,22 @@ const CommonTestAndPreview = (props) => {
|
|
|
218
321
|
[CHANNELS.WHATSAPP]: {
|
|
219
322
|
domainId: null, senderMobNum: '', sourceAccountIdentifier: '',
|
|
220
323
|
},
|
|
324
|
+
[CHANNELS.RCS]: {
|
|
325
|
+
domainId: null,
|
|
326
|
+
domainGatewayMapId: null,
|
|
327
|
+
gsmSenderId: '',
|
|
328
|
+
smsFallbackDomainId: null,
|
|
329
|
+
cdmaSenderId: '', // gsmSenderId = RCS sender (domainId|senderId), cdmaSenderId = SMS fallback
|
|
330
|
+
},
|
|
221
331
|
});
|
|
222
332
|
|
|
223
|
-
const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP];
|
|
333
|
+
const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS];
|
|
224
334
|
const formDataForSendTest = formData ?? (content && typeof content === 'object' && !Array.isArray(content) ? content : formData);
|
|
225
335
|
const smsTemplateConfigs = formDataForSendTest?.templateConfigs || {};
|
|
226
336
|
const smsTraiDltEnabled = !!smsTemplateConfigs?.traiDltEnabled;
|
|
227
337
|
const registeredSenderIds = smsTemplateConfigs?.registeredSenderIds || [];
|
|
228
338
|
|
|
229
|
-
// Fetch sender details and WeCRM accounts when Test & Preview opens
|
|
339
|
+
// Fetch sender details and WeCRM accounts when Test & Preview opens (SMS, Email, WhatsApp, RCS — same process)
|
|
230
340
|
useEffect(() => {
|
|
231
341
|
if (!show || !channel) {
|
|
232
342
|
return;
|
|
@@ -235,39 +345,99 @@ const CommonTestAndPreview = (props) => {
|
|
|
235
345
|
if (actions.getSenderDetailsRequested) {
|
|
236
346
|
actions.getSenderDetailsRequested({ channel, orgUnitId: orgUnitId ?? -1 });
|
|
237
347
|
}
|
|
348
|
+
// SMS domains/senders are needed for RCS delivery UI (fallback row + slidebox) whenever RCS is open — not only when fallback body exists.
|
|
349
|
+
if (channel === CHANNELS.RCS && actions.getSenderDetailsRequested) {
|
|
350
|
+
actions.getSenderDetailsRequested({ channel: CHANNELS.SMS, orgUnitId: orgUnitId ?? -1 });
|
|
351
|
+
}
|
|
238
352
|
if (channel === CHANNELS.WHATSAPP && actions.getWeCrmAccountsRequested) {
|
|
239
353
|
actions.getWeCrmAccountsRequested({ sourceName: CHANNELS.WHATSAPP });
|
|
240
354
|
}
|
|
241
355
|
}
|
|
242
356
|
}, [show, channel, orgUnitId, actions]);
|
|
243
357
|
|
|
244
|
-
useEffect(() => {
|
|
245
|
-
if (!show) {
|
|
246
|
-
setCustomerModal([false, '']);
|
|
247
|
-
setSearchValue('');
|
|
248
|
-
setCustomerData({ name: '', email: '', mobile: '', customerId: '' });
|
|
249
|
-
}
|
|
250
|
-
}, [show]);
|
|
251
|
-
|
|
252
358
|
const findDefault = (arr) => (arr && arr.find((x) => x.default)) || (arr && arr[0]) || {};
|
|
253
359
|
|
|
254
360
|
// Auto-set default delivery setting when sender details load (campaigns-style: first domain + default/first sender)
|
|
255
361
|
useEffect(() => {
|
|
256
362
|
if (!channel || !channelsWithDeliverySettings.includes(channel)) return;
|
|
363
|
+
|
|
364
|
+
if (channel === CHANNELS.RCS) {
|
|
365
|
+
const rcsDomainRows = senderDetailsByChannel?.[CHANNELS.RCS] || [];
|
|
366
|
+
const smsFallbackDomainRows = senderDetailsByChannel?.[CHANNELS.SMS] || [];
|
|
367
|
+
if (!rcsDomainRows.length) return;
|
|
368
|
+
|
|
369
|
+
const currentRcsDeliverySettings = testPreviewDeliverySettings?.[CHANNELS.RCS] || {};
|
|
370
|
+
const isRcsGsmSenderUnset = !currentRcsDeliverySettings?.gsmSenderId;
|
|
371
|
+
const isSmsFallbackSenderUnset = !currentRcsDeliverySettings?.cdmaSenderId;
|
|
372
|
+
const isRcsDeliveryFullyUnset = isRcsGsmSenderUnset && isSmsFallbackSenderUnset;
|
|
373
|
+
const shouldOnlyFillSmsFallbackSender =
|
|
374
|
+
!isRcsGsmSenderUnset && isSmsFallbackSenderUnset && smsFallbackDomainRows.length > 0;
|
|
375
|
+
|
|
376
|
+
if (!isRcsDeliveryFullyUnset && !shouldOnlyFillSmsFallbackSender) return;
|
|
377
|
+
|
|
378
|
+
const firstRcsDomain = rcsDomainRows[0];
|
|
379
|
+
const firstSmsFallbackDomain = smsFallbackDomainRows[0];
|
|
380
|
+
const usableRcsGsmSenders = filterUsableGsmSendersForDomain(
|
|
381
|
+
firstRcsDomain,
|
|
382
|
+
firstRcsDomain?.gsmSenders,
|
|
383
|
+
{ skipDomainNameEchoFilter: true },
|
|
384
|
+
);
|
|
385
|
+
const usableSmsFallbackGsmSenders = firstSmsFallbackDomain
|
|
386
|
+
? filterUsableGsmSendersForDomain(firstSmsFallbackDomain, firstSmsFallbackDomain?.gsmSenders)
|
|
387
|
+
: [];
|
|
388
|
+
const defaultRcsGsmSender = usableRcsGsmSenders[0];
|
|
389
|
+
const defaultSmsFallbackGsmSender = usableSmsFallbackGsmSenders[0];
|
|
390
|
+
const rcsSenderCompositeValue =
|
|
391
|
+
firstRcsDomain?.domainId != null && defaultRcsGsmSender?.value != null
|
|
392
|
+
? `${firstRcsDomain.domainId}|${defaultRcsGsmSender.value}`
|
|
393
|
+
: (defaultRcsGsmSender?.value || '');
|
|
394
|
+
const smsFallbackSenderCompositeValue =
|
|
395
|
+
firstSmsFallbackDomain?.domainId != null && defaultSmsFallbackGsmSender?.value != null
|
|
396
|
+
? `${firstSmsFallbackDomain.domainId}|${defaultSmsFallbackGsmSender.value}`
|
|
397
|
+
: (defaultSmsFallbackGsmSender?.value || '');
|
|
398
|
+
|
|
399
|
+
setTestPreviewDeliverySettings((prev) => {
|
|
400
|
+
const previousRcsSettings = prev?.[CHANNELS.RCS] || {};
|
|
401
|
+
if (shouldOnlyFillSmsFallbackSender) {
|
|
402
|
+
if (!smsFallbackSenderCompositeValue) return prev;
|
|
403
|
+
return {
|
|
404
|
+
...prev,
|
|
405
|
+
[CHANNELS.RCS]: {
|
|
406
|
+
...previousRcsSettings,
|
|
407
|
+
smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
|
|
408
|
+
cdmaSenderId: smsFallbackSenderCompositeValue,
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
...prev,
|
|
414
|
+
[CHANNELS.RCS]: {
|
|
415
|
+
domainId: firstRcsDomain?.domainId ?? null,
|
|
416
|
+
domainGatewayMapId: firstRcsDomain?.dgmId ?? null,
|
|
417
|
+
gsmSenderId: rcsSenderCompositeValue,
|
|
418
|
+
smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
|
|
419
|
+
cdmaSenderId: smsFallbackSenderCompositeValue,
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
257
426
|
const domains = senderDetailsByChannel[channel];
|
|
258
427
|
if (!domains || domains.length === 0) return;
|
|
259
428
|
const {
|
|
260
|
-
domainId = '', gsmSenderId = '', senderEmail = '', senderMobNum = '',
|
|
429
|
+
domainId = '', gsmSenderId = '', cdmaSenderId = '', senderEmail = '', senderMobNum = '',
|
|
261
430
|
} = testPreviewDeliverySettings[channel] || {};
|
|
262
|
-
const isEmptySelection = !domainId && !gsmSenderId && !senderEmail && !senderMobNum;
|
|
431
|
+
const isEmptySelection = !domainId && !gsmSenderId && !cdmaSenderId && !senderEmail && !senderMobNum;
|
|
263
432
|
if (!isEmptySelection) return;
|
|
264
433
|
|
|
265
434
|
const whatsappAccountFromForm = channel === CHANNELS.WHATSAPP ? formData?.accountName : undefined;
|
|
266
435
|
const matchedWhatsappAccount = whatsappAccountFromForm
|
|
267
436
|
? (wecrmAccounts || []).find((account) => account?.name === whatsappAccountFromForm)
|
|
268
437
|
: null;
|
|
269
|
-
const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled
|
|
270
|
-
? domains.filter((domain) => (domain
|
|
438
|
+
const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled && registeredSenderIds?.length
|
|
439
|
+
? domains.filter((domain) => (domain?.gsmSenders || []).some((gsm) =>
|
|
440
|
+
registeredSenderIds?.includes(gsm?.value)))
|
|
271
441
|
: domains;
|
|
272
442
|
const [defaultDomain] = domains;
|
|
273
443
|
const [firstSmsDomain] = smsDomains;
|
|
@@ -282,8 +452,8 @@ const CommonTestAndPreview = (props) => {
|
|
|
282
452
|
const next = { ...prev };
|
|
283
453
|
if (channel === CHANNELS.SMS) {
|
|
284
454
|
const smsGsmSenders = smsTraiDltEnabled
|
|
285
|
-
? (firstDomain
|
|
286
|
-
: firstDomain
|
|
455
|
+
? (firstDomain?.gsmSenders || []).filter((gsm) => registeredSenderIds?.includes(gsm?.value))
|
|
456
|
+
: firstDomain?.gsmSenders;
|
|
287
457
|
next[channel] = {
|
|
288
458
|
domainId: firstDomain.domainId,
|
|
289
459
|
domainGatewayMapId: firstDomain.dgmId,
|
|
@@ -316,10 +486,29 @@ const CommonTestAndPreview = (props) => {
|
|
|
316
486
|
// MEMOIZED VALUES
|
|
317
487
|
// ============================================
|
|
318
488
|
|
|
489
|
+
const allTags = useMemo(
|
|
490
|
+
() => [...requiredTags, ...optionalTags, ...smsFallbackRequiredTags, ...smsFallbackOptionalTags],
|
|
491
|
+
[requiredTags, optionalTags, smsFallbackRequiredTags, smsFallbackOptionalTags]
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
const allRequiredTags = useMemo(
|
|
495
|
+
() => [...requiredTags, ...smsFallbackRequiredTags],
|
|
496
|
+
[requiredTags, smsFallbackRequiredTags]
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
const buildEmptyValues = useCallback(
|
|
500
|
+
() => allTags.reduce((acc, tag) => {
|
|
501
|
+
const key = tag?.fullPath;
|
|
502
|
+
if (key) acc[key] = '';
|
|
503
|
+
return acc;
|
|
504
|
+
}, {}),
|
|
505
|
+
[allTags]
|
|
506
|
+
);
|
|
507
|
+
|
|
319
508
|
// Check if update preview button should be disabled
|
|
320
509
|
const isUpdatePreviewDisabled = useMemo(() => (
|
|
321
|
-
|
|
322
|
-
), [
|
|
510
|
+
allRequiredTags.some((tag) => !customValues[tag.fullPath])
|
|
511
|
+
), [allRequiredTags, customValues]);
|
|
323
512
|
|
|
324
513
|
// Get current content based on channel and editor type
|
|
325
514
|
const getCurrentContent = useMemo(() => {
|
|
@@ -363,6 +552,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
363
552
|
return currentTabData.base['sms-editor'];
|
|
364
553
|
}
|
|
365
554
|
}
|
|
555
|
+
// DLT / Test & Preview shape: { templateConfigs: { template, templateId, ... } }
|
|
556
|
+
if (formData.templateConfigs?.template) {
|
|
557
|
+
const smsDltTemplateValue = formData.templateConfigs.template;
|
|
558
|
+
if (typeof smsDltTemplateValue === 'string') return smsDltTemplateValue;
|
|
559
|
+
if (Array.isArray(smsDltTemplateValue)) return smsDltTemplateValue.join('');
|
|
560
|
+
return '';
|
|
561
|
+
}
|
|
366
562
|
}
|
|
367
563
|
|
|
368
564
|
// SMS channel fallback - if formData is not available, use content directly
|
|
@@ -408,7 +604,70 @@ const CommonTestAndPreview = (props) => {
|
|
|
408
604
|
return content || '';
|
|
409
605
|
}, [channel, formData, currentTab, beeContent, content, beeInstance]);
|
|
410
606
|
|
|
411
|
-
|
|
607
|
+
const leftPanelExtractedTags = useMemo(() => {
|
|
608
|
+
if (channel === CHANNELS.SMS) {
|
|
609
|
+
const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
|
|
610
|
+
if (!smsTemplateHasMustacheTags(smsEditorBody)) return [];
|
|
611
|
+
const extractTagsFromApi = extractedTags ?? [];
|
|
612
|
+
if (extractTagsFromApi.length > 0) return extractTagsFromApi;
|
|
613
|
+
return buildSyntheticSmsMustacheTags(smsEditorBody);
|
|
614
|
+
}
|
|
615
|
+
const hasFallbackSmsBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
616
|
+
if (channel === CHANNELS.RCS && hasFallbackSmsBody) {
|
|
617
|
+
const rcsPrimaryTags = extractedTags ?? [];
|
|
618
|
+
const fallbackSmsTextForTags = smsFallbackTextForTagExtraction ?? '';
|
|
619
|
+
const fallbackSmsTagRows = smsTemplateHasMustacheTags(fallbackSmsTextForTags)
|
|
620
|
+
? (smsFallbackExtractedTags?.length > 0
|
|
621
|
+
? smsFallbackExtractedTags
|
|
622
|
+
: buildSyntheticSmsMustacheTags(fallbackSmsTextForTags))
|
|
623
|
+
: [];
|
|
624
|
+
const mergedRcsAndFallbackTags = [...rcsPrimaryTags, ...fallbackSmsTagRows];
|
|
625
|
+
if (mergedRcsAndFallbackTags.length > 0) return mergedRcsAndFallbackTags;
|
|
626
|
+
return buildSyntheticSmsMustacheTags(fallbackSmsTextForTags);
|
|
627
|
+
}
|
|
628
|
+
return extractedTags ?? [];
|
|
629
|
+
}, [
|
|
630
|
+
channel,
|
|
631
|
+
extractedTags,
|
|
632
|
+
getCurrentContent,
|
|
633
|
+
smsFallbackContent,
|
|
634
|
+
smsFallbackExtractedTags,
|
|
635
|
+
smsFallbackTextForTagExtraction,
|
|
636
|
+
]);
|
|
637
|
+
|
|
638
|
+
const isRcsSmsFallbackPreviewEnabled =
|
|
639
|
+
channel === CHANNELS.RCS
|
|
640
|
+
&& !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
641
|
+
// Only treat as SMS when user is on the Fallback SMS tab — not whenever fallback exists (RCS tab needs RCS preview API).
|
|
642
|
+
const isSmsFallbackTabActive = isRcsSmsFallbackPreviewEnabled && activePreviewTab === PREVIEW_TAB_SMS_FALLBACK;
|
|
643
|
+
const activeChannelForActions = isSmsFallbackTabActive ? CHANNELS.SMS : channel;
|
|
644
|
+
// VarSegment slot values live in rcsSmsFallbackVarMapped; raw templateContent alone is stale for /preview Body.
|
|
645
|
+
const resolvedSmsFallbackBodyForPreviewTab =
|
|
646
|
+
smsFallbackTextForTagExtraction
|
|
647
|
+
|| smsFallbackContent?.templateContent
|
|
648
|
+
|| smsFallbackContent?.content
|
|
649
|
+
|| '';
|
|
650
|
+
const activeContentForActions = isSmsFallbackTabActive
|
|
651
|
+
? resolvedSmsFallbackBodyForPreviewTab
|
|
652
|
+
: getCurrentContent;
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* SMS fallback pane must show /preview API result when user updated preview on that tab (plain text).
|
|
656
|
+
* Skip when resolvedBody is RCS-shaped (e.g. user last previewed on RCS tab).
|
|
657
|
+
*/
|
|
658
|
+
// smsFallbackPreviewText is the single source of truth for the resolved SMS fallback preview.
|
|
659
|
+
// It is set only by syncSmsFallbackPreview (called from handleUpdatePreview and the
|
|
660
|
+
// prefilled-values effect) and reset to undefined on discard / slidebox close.
|
|
661
|
+
// Using previewDataHtml as a fallback is unsafe because that state is shared with the primary
|
|
662
|
+
// RCS preview and can contain stale SMS or RCS content.
|
|
663
|
+
const smsFallbackResolvedText = useMemo(() => {
|
|
664
|
+
const hasFallbackBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
665
|
+
if (channel !== CHANNELS.RCS || !hasFallbackBody) return undefined;
|
|
666
|
+
if (smsFallbackPreviewText != null) return smsFallbackPreviewText;
|
|
667
|
+
return undefined;
|
|
668
|
+
}, [channel, smsFallbackContent, smsFallbackPreviewText]);
|
|
669
|
+
|
|
670
|
+
// Build test entities tree data from testCustomers prop
|
|
412
671
|
// Build test entities tree data from testCustomers prop (includes customers added via addTestCustomer action)
|
|
413
672
|
const testEntitiesTreeData = useMemo(() => {
|
|
414
673
|
const groupsNode = {
|
|
@@ -417,7 +676,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
417
676
|
selectable: false,
|
|
418
677
|
children: testGroups?.map((group) => ({
|
|
419
678
|
title: group?.groupName,
|
|
420
|
-
value:
|
|
679
|
+
value: normalizeTestEntityId(group?.groupId),
|
|
421
680
|
})),
|
|
422
681
|
};
|
|
423
682
|
|
|
@@ -427,7 +686,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
427
686
|
selectable: false,
|
|
428
687
|
children: testCustomers?.map((customer) => ({
|
|
429
688
|
title: customer?.name?.trim() || customer?.email?.trim() || customer?.mobile?.trim() || customer?.userId || customer?.customerId,
|
|
430
|
-
value:
|
|
689
|
+
value: normalizeTestEntityId(customer?.userId ?? customer?.customerId),
|
|
431
690
|
})) || [],
|
|
432
691
|
};
|
|
433
692
|
|
|
@@ -507,11 +766,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
507
766
|
email: customerData?.email || '',
|
|
508
767
|
mobile: customerData?.mobile || '',
|
|
509
768
|
});
|
|
510
|
-
const prefixedAddedId = 'customer:' + normalizedAddedId;
|
|
511
769
|
setSelectedTestEntities((prev) => (
|
|
512
|
-
prev.some((id) => id
|
|
770
|
+
prev.some((id) => testEntityIdsEqual(id, normalizedAddedId))
|
|
513
771
|
? prev
|
|
514
|
-
: [...prev,
|
|
772
|
+
: [...prev, normalizedAddedId]
|
|
515
773
|
));
|
|
516
774
|
}
|
|
517
775
|
handleCloseCustomerModal();
|
|
@@ -615,6 +873,37 @@ const CommonTestAndPreview = (props) => {
|
|
|
615
873
|
}
|
|
616
874
|
};
|
|
617
875
|
|
|
876
|
+
/**
|
|
877
|
+
* When RCS has SMS fallback, refresh fallback preview text via the same Liquid /preview API
|
|
878
|
+
* (separate call with SMS channel + fallback template body). Used after primary preview updates.
|
|
879
|
+
*/
|
|
880
|
+
const syncSmsFallbackPreview = async (customValuesForResolve, selectedCustomerObj) => {
|
|
881
|
+
const fallbackBodyForLiquidPreview =
|
|
882
|
+
getSmsFallbackTextForTagExtraction(smsFallbackContent)
|
|
883
|
+
|| smsFallbackContent?.templateContent
|
|
884
|
+
|| smsFallbackContent?.content
|
|
885
|
+
|| '';
|
|
886
|
+
if (channel !== CHANNELS.RCS || !String(fallbackBodyForLiquidPreview).trim()) return;
|
|
887
|
+
try {
|
|
888
|
+
const smsFallbackPayload = preparePreviewPayload(
|
|
889
|
+
CHANNELS.SMS,
|
|
890
|
+
formData || {},
|
|
891
|
+
fallbackBodyForLiquidPreview,
|
|
892
|
+
customValuesForResolve,
|
|
893
|
+
selectedCustomerObj
|
|
894
|
+
);
|
|
895
|
+
const fallbackResponse = await Api.updateEmailPreview(smsFallbackPayload);
|
|
896
|
+
const fallbackPreview = extractPreviewFromLiquidResponse(fallbackResponse);
|
|
897
|
+
setSmsFallbackPreviewText(
|
|
898
|
+
typeof fallbackPreview?.resolvedBody === 'string'
|
|
899
|
+
? fallbackPreview.resolvedBody
|
|
900
|
+
: undefined
|
|
901
|
+
);
|
|
902
|
+
} catch (e) {
|
|
903
|
+
/* keep existing smsFallbackPreviewText on failure */
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
|
|
618
907
|
/**
|
|
619
908
|
* Prepare payload for tag extraction based on channel
|
|
620
909
|
*/
|
|
@@ -709,7 +998,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
709
998
|
} = carousel || {};
|
|
710
999
|
const buttonData = buttons.map((button, index) => {
|
|
711
1000
|
const {
|
|
712
|
-
type, text, phone_number, urlType, url,
|
|
1001
|
+
type, text, phone_number: phoneNumber, urlType, url,
|
|
713
1002
|
} = button || {};
|
|
714
1003
|
const buttonObj = {
|
|
715
1004
|
type,
|
|
@@ -717,7 +1006,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
717
1006
|
index,
|
|
718
1007
|
};
|
|
719
1008
|
if (type === PHONE_NUMBER) {
|
|
720
|
-
buttonObj.phoneNumber =
|
|
1009
|
+
buttonObj.phoneNumber = phoneNumber;
|
|
721
1010
|
}
|
|
722
1011
|
if (type === URL) {
|
|
723
1012
|
buttonObj.url = url;
|
|
@@ -746,7 +1035,130 @@ const CommonTestAndPreview = (props) => {
|
|
|
746
1035
|
};
|
|
747
1036
|
});
|
|
748
1037
|
|
|
749
|
-
|
|
1038
|
+
/**
|
|
1039
|
+
* Build createMessageMeta payload for RCS (test message).
|
|
1040
|
+
* rcsMessageContent: { channel, accountId?, rcsRichCardContent: { contentType, cardType, cardSettings, cardContent }, smsFallBackContent? }
|
|
1041
|
+
* Then rcsDeliverySettings, executionParams, clientName last.
|
|
1042
|
+
*/
|
|
1043
|
+
const buildRcsTestMessagePayload = (
|
|
1044
|
+
creativeFormData,
|
|
1045
|
+
_unusedEditorContentString,
|
|
1046
|
+
_customValuesObj,
|
|
1047
|
+
deliverySettingsOverride,
|
|
1048
|
+
basePayload,
|
|
1049
|
+
_rcsTestMetaExtras = {},
|
|
1050
|
+
) => {
|
|
1051
|
+
const rcsSectionFromForm =
|
|
1052
|
+
creativeFormData?.versions?.base?.content?.RCS ?? creativeFormData?.content?.RCS ?? {};
|
|
1053
|
+
const rcsContentFromForm = rcsSectionFromForm?.rcsContent || {};
|
|
1054
|
+
const smsFallbackFromCreativeForm = rcsSectionFromForm?.smsFallBackContent || {};
|
|
1055
|
+
let rcsCardPayloadList = [];
|
|
1056
|
+
if (Array.isArray(rcsContentFromForm?.cardContent)) {
|
|
1057
|
+
rcsCardPayloadList = rcsContentFromForm.cardContent;
|
|
1058
|
+
} else if (rcsContentFromForm?.cardContent) {
|
|
1059
|
+
rcsCardPayloadList = [rcsContentFromForm.cardContent];
|
|
1060
|
+
}
|
|
1061
|
+
// Raw title/description with template tags; SMS fallback uses tagged template fields (pickFirst…).
|
|
1062
|
+
const cardContentForTestMetaApi = rcsCardPayloadList.map((singleRcsCardPayload) => {
|
|
1063
|
+
const normalizedCardMediaForTestApi = singleRcsCardPayload?.media
|
|
1064
|
+
? normalizeRcsTestCardMedia(singleRcsCardPayload.media)
|
|
1065
|
+
: undefined;
|
|
1066
|
+
const suggestionsFromCard = Array.isArray(singleRcsCardPayload?.suggestions)
|
|
1067
|
+
? singleRcsCardPayload.suggestions
|
|
1068
|
+
: [];
|
|
1069
|
+
const suggestionsFormattedForTestMeta = suggestionsFromCard.map((suggestionItem, index) =>
|
|
1070
|
+
mapRcsSuggestionForTestMeta(suggestionItem, index));
|
|
1071
|
+
return {
|
|
1072
|
+
title: singleRcsCardPayload?.title ?? '',
|
|
1073
|
+
description: singleRcsCardPayload?.description ?? '',
|
|
1074
|
+
mediaType: singleRcsCardPayload?.mediaType ?? MEDIA_TYPE_TEXT,
|
|
1075
|
+
...(normalizedCardMediaForTestApi && { media: normalizedCardMediaForTestApi }),
|
|
1076
|
+
...(suggestionsFormattedForTestMeta.length > 0 && {
|
|
1077
|
+
suggestions: suggestionsFormattedForTestMeta,
|
|
1078
|
+
}),
|
|
1079
|
+
};
|
|
1080
|
+
});
|
|
1081
|
+
const smsFallbackTaggedTemplateBody = pickFirstSmsFallbackTemplateString(
|
|
1082
|
+
smsFallbackFromCreativeForm,
|
|
1083
|
+
) || '';
|
|
1084
|
+
const smsSenderFromDelivery = deliverySettingsOverride?.cdmaSenderId?.includes('|')
|
|
1085
|
+
? deliverySettingsOverride.cdmaSenderId.split('|')[1]
|
|
1086
|
+
: deliverySettingsOverride?.cdmaSenderId;
|
|
1087
|
+
const deliveryFallbackSmsId =
|
|
1088
|
+
typeof smsSenderFromDelivery === 'string' ? smsSenderFromDelivery.trim() : '';
|
|
1089
|
+
const creativeFallbackSmsId =
|
|
1090
|
+
smsFallbackFromCreativeForm?.senderId != null
|
|
1091
|
+
? String(smsFallbackFromCreativeForm.senderId).trim()
|
|
1092
|
+
: '';
|
|
1093
|
+
const fallbackSmsSenderIdForChannel = deliveryFallbackSmsId || creativeFallbackSmsId || '';
|
|
1094
|
+
|
|
1095
|
+
const smsFallBackContent =
|
|
1096
|
+
smsFallbackTaggedTemplateBody.trim() !== ''
|
|
1097
|
+
? { message: smsFallbackTaggedTemplateBody }
|
|
1098
|
+
: undefined;
|
|
1099
|
+
|
|
1100
|
+
// accountId: WeCRM account id (not sourceAccountIdentifier) for createMessageMeta
|
|
1101
|
+
const accountIdForMeta =
|
|
1102
|
+
rcsContentFromForm?.accountId != null && String(rcsContentFromForm.accountId).trim() !== ''
|
|
1103
|
+
? String(rcsContentFromForm.accountId)
|
|
1104
|
+
: undefined;
|
|
1105
|
+
|
|
1106
|
+
const rcsRichCardContent = {
|
|
1107
|
+
contentType: RCS_TEST_META_CONTENT_TYPE_RICHCARD,
|
|
1108
|
+
cardType: rcsContentFromForm?.cardType ?? RCS_TEST_META_CARD_TYPE_STANDALONE,
|
|
1109
|
+
cardSettings: rcsContentFromForm?.cardSettings ?? {
|
|
1110
|
+
cardOrientation: RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
|
|
1111
|
+
cardWidth: RCS_TEST_META_CARD_WIDTH_SMALL,
|
|
1112
|
+
},
|
|
1113
|
+
...(cardContentForTestMetaApi.length > 0 && { cardContent: cardContentForTestMetaApi }),
|
|
1114
|
+
};
|
|
1115
|
+
|
|
1116
|
+
const rcsMessageContent = {
|
|
1117
|
+
channel: CHANNELS.RCS,
|
|
1118
|
+
...(accountIdForMeta && { accountId: accountIdForMeta }),
|
|
1119
|
+
rcsRichCardContent,
|
|
1120
|
+
...(smsFallBackContent && { smsFallBackContent }),
|
|
1121
|
+
};
|
|
1122
|
+
const rcsComposite = deliverySettingsOverride?.gsmSenderId ?? '';
|
|
1123
|
+
const [rcsDomainId, rcsSenderId] = rcsComposite.includes('|') ? rcsComposite.split('|') : ['', rcsComposite];
|
|
1124
|
+
const rcsDeliverySettings = {
|
|
1125
|
+
channelSettings: {
|
|
1126
|
+
channel: CHANNELS.RCS,
|
|
1127
|
+
rcsSender: (rcsSenderId || deliverySettingsOverride?.rcsSender) ?? '',
|
|
1128
|
+
domainId:
|
|
1129
|
+
rcsDomainId !== '' && rcsDomainId !== undefined && !Number.isNaN(Number(rcsDomainId))
|
|
1130
|
+
? Number(rcsDomainId)
|
|
1131
|
+
: (deliverySettingsOverride?.domainId ?? 0),
|
|
1132
|
+
fallbackSmsSenderId: fallbackSmsSenderIdForChannel,
|
|
1133
|
+
},
|
|
1134
|
+
additionalSettings: {
|
|
1135
|
+
useTinyUrl: false,
|
|
1136
|
+
encryptUrl: false,
|
|
1137
|
+
linkTrackingEnabled: false,
|
|
1138
|
+
bypassControlUser: false,
|
|
1139
|
+
userSubscriptionDisabled: false,
|
|
1140
|
+
},
|
|
1141
|
+
};
|
|
1142
|
+
const { clientName: baseClientName = CLIENT_NAME_CREATIVES, ...restBase } = basePayload;
|
|
1143
|
+
return {
|
|
1144
|
+
...restBase,
|
|
1145
|
+
rcsMessageContent,
|
|
1146
|
+
rcsDeliverySettings,
|
|
1147
|
+
executionParams: {},
|
|
1148
|
+
clientName: baseClientName,
|
|
1149
|
+
};
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
const prepareTestMessagePayload = (
|
|
1153
|
+
channelType,
|
|
1154
|
+
formDataObj,
|
|
1155
|
+
contentStr,
|
|
1156
|
+
customValuesObj,
|
|
1157
|
+
recipientDetails,
|
|
1158
|
+
previewDataObj,
|
|
1159
|
+
deliverySettingsOverride,
|
|
1160
|
+
rcsExtra = {},
|
|
1161
|
+
) => {
|
|
750
1162
|
// Base payload structure common to all channels
|
|
751
1163
|
const basePayload = {
|
|
752
1164
|
ouId: -1,
|
|
@@ -827,12 +1239,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
827
1239
|
},
|
|
828
1240
|
smsDeliverySettings: {
|
|
829
1241
|
channelSettings: {
|
|
1242
|
+
channel: CHANNELS.SMS,
|
|
830
1243
|
gsmSenderId: deliverySettingsOverride?.gsmSenderId ?? '',
|
|
831
1244
|
domainId: deliverySettingsOverride?.domainId ?? null,
|
|
832
1245
|
domainGatewayMapId: deliverySettingsOverride?.domainGatewayMapId ?? '',
|
|
833
1246
|
targetNdnc: false,
|
|
834
1247
|
cdmaSenderId: deliverySettingsOverride?.cdmaSenderId ?? '',
|
|
835
|
-
channel: CHANNELS.SMS,
|
|
836
1248
|
},
|
|
837
1249
|
additionalSettings: {
|
|
838
1250
|
useTinyUrl: false,
|
|
@@ -976,7 +1388,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
976
1388
|
return {
|
|
977
1389
|
...basePayload,
|
|
978
1390
|
whatsappMessageContent: {
|
|
979
|
-
messageBody: templateEditorValue || '',
|
|
1391
|
+
messageBody: resolvedMessageBody || templateEditorValue || '',
|
|
980
1392
|
accountId: formDataObj?.accountId || '',
|
|
981
1393
|
sourceAccountIdentifier: sourceAccountIdentifier || formDataObj?.sourceAccountIdentifier || '',
|
|
982
1394
|
accountName: formDataObj?.accountName || '',
|
|
@@ -1001,16 +1413,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
1001
1413
|
}
|
|
1002
1414
|
|
|
1003
1415
|
case CHANNELS.RCS:
|
|
1004
|
-
return
|
|
1005
|
-
...basePayload,
|
|
1006
|
-
rcsMessageContent: {
|
|
1007
|
-
channel: CHANNELS.RCS,
|
|
1008
|
-
messageBody: contentStr,
|
|
1009
|
-
rcsType: additionalProps?.rcsType,
|
|
1010
|
-
rcsImageSrc: formDataObj?.rcsImageSrc,
|
|
1011
|
-
rcsSuggestions: formDataObj?.rcsSuggestions,
|
|
1012
|
-
},
|
|
1013
|
-
};
|
|
1416
|
+
return buildRcsTestMessagePayload(formDataObj, contentStr, customValuesObj, deliverySettingsOverride, basePayload, rcsExtra);
|
|
1014
1417
|
|
|
1015
1418
|
case CHANNELS.INAPP: {
|
|
1016
1419
|
// InApp payload structure similar to MobilePush
|
|
@@ -2112,6 +2515,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
2112
2515
|
formatMessage,
|
|
2113
2516
|
lastModified: formData?.lastModified,
|
|
2114
2517
|
updatedByName: formData?.updatedByName,
|
|
2518
|
+
smsFallbackContent: isRcsSmsFallbackPreviewEnabled ? smsFallbackContent : null,
|
|
2519
|
+
smsFallbackResolvedText,
|
|
2520
|
+
activePreviewTab,
|
|
2521
|
+
onPreviewTabChange: setActivePreviewTab,
|
|
2115
2522
|
};
|
|
2116
2523
|
};
|
|
2117
2524
|
|
|
@@ -2151,7 +2558,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
2151
2558
|
}, [show, beeInstance, currentTab, channel]);
|
|
2152
2559
|
|
|
2153
2560
|
/**
|
|
2154
|
-
* Initial data load when slidebox opens
|
|
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.
|
|
2155
2567
|
*/
|
|
2156
2568
|
useEffect(() => {
|
|
2157
2569
|
if (show) {
|
|
@@ -2236,7 +2648,61 @@ const CommonTestAndPreview = (props) => {
|
|
|
2236
2648
|
actions.getTestGroupsRequested();
|
|
2237
2649
|
}
|
|
2238
2650
|
}
|
|
2239
|
-
|
|
2651
|
+
// getCurrentContent: RCS applies cardVarMapped → placeholder resolution; re-extract when it changes.
|
|
2652
|
+
}, [show, beeInstance, currentTab, channel, getCurrentContent]);
|
|
2653
|
+
|
|
2654
|
+
/**
|
|
2655
|
+
* RCS with SMS fallback: extract tags for fallback SMS content as well
|
|
2656
|
+
* (so we can show a separate "Fallback SMS tags" section in left panel).
|
|
2657
|
+
*/
|
|
2658
|
+
useEffect(() => {
|
|
2659
|
+
let cancelled = false;
|
|
2660
|
+
|
|
2661
|
+
if (!show || channel !== CHANNELS.RCS) {
|
|
2662
|
+
return () => {
|
|
2663
|
+
cancelled = true;
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
if (!smsFallbackContent?.templateContent && !smsFallbackContent?.content) {
|
|
2668
|
+
setSmsFallbackExtractedTags([]);
|
|
2669
|
+
setSmsFallbackRequiredTags([]);
|
|
2670
|
+
setSmsFallbackOptionalTags([]);
|
|
2671
|
+
return () => {
|
|
2672
|
+
cancelled = true;
|
|
2673
|
+
};
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
setIsExtractingSmsFallbackTags(true);
|
|
2677
|
+
(async () => {
|
|
2678
|
+
try {
|
|
2679
|
+
const fallbackBodyForExtractApi = getSmsFallbackTextForTagExtraction(smsFallbackContent);
|
|
2680
|
+
const payload = {
|
|
2681
|
+
messageTitle: '',
|
|
2682
|
+
messageBody: fallbackBodyForExtractApi || '',
|
|
2683
|
+
};
|
|
2684
|
+
const response = await Api.extractTagsWithMetaData(payload); //not using saga action here because we dont store fallbacksms related data in store but only in useState since this is only used in RCS SMS fallback
|
|
2685
|
+
let smsFallbackTagTree = response?.data ?? [];
|
|
2686
|
+
if (!Array.isArray(smsFallbackTagTree)) smsFallbackTagTree = [];
|
|
2687
|
+
if (!smsTemplateHasMustacheTags(fallbackBodyForExtractApi)) {
|
|
2688
|
+
smsFallbackTagTree = [];
|
|
2689
|
+
} else if (smsFallbackTagTree.length === 0) {
|
|
2690
|
+
smsFallbackTagTree = buildSyntheticSmsMustacheTags(fallbackBodyForExtractApi);
|
|
2691
|
+
}
|
|
2692
|
+
if (cancelled) return;
|
|
2693
|
+
setSmsFallbackExtractedTags(smsFallbackTagTree);
|
|
2694
|
+
} catch (e) {
|
|
2695
|
+
if (cancelled) return;
|
|
2696
|
+
setSmsFallbackExtractedTags([]);
|
|
2697
|
+
} finally {
|
|
2698
|
+
if (!cancelled) setIsExtractingSmsFallbackTags(false);
|
|
2699
|
+
}
|
|
2700
|
+
})();
|
|
2701
|
+
|
|
2702
|
+
return () => {
|
|
2703
|
+
cancelled = true;
|
|
2704
|
+
};
|
|
2705
|
+
}, [show, channel, smsFallbackContent]);
|
|
2240
2706
|
|
|
2241
2707
|
/**
|
|
2242
2708
|
* Email-specific: Handle content updates for both BEE and CKEditor
|
|
@@ -2288,15 +2754,22 @@ const CommonTestAndPreview = (props) => {
|
|
|
2288
2754
|
setSelectedCustomer(null);
|
|
2289
2755
|
setRequiredTags([]);
|
|
2290
2756
|
setOptionalTags([]);
|
|
2757
|
+
setSmsFallbackExtractedTags([]);
|
|
2758
|
+
setSmsFallbackRequiredTags([]);
|
|
2759
|
+
setSmsFallbackOptionalTags([]);
|
|
2760
|
+
setIsExtractingSmsFallbackTags(false);
|
|
2291
2761
|
setCustomValues({});
|
|
2292
2762
|
setShowJSON(false);
|
|
2293
2763
|
setTagsExtracted(false);
|
|
2294
2764
|
setPreviewDevice(DESKTOP);
|
|
2765
|
+
setActivePreviewTab(PREVIEW_TAB_RCS);
|
|
2766
|
+
setSmsFallbackPreviewText(undefined);
|
|
2295
2767
|
setSelectedTestEntities([]);
|
|
2296
2768
|
actions.clearPrefilledValues();
|
|
2297
2769
|
} else {
|
|
2298
2770
|
// Reset device to initialDevice when opening (Android for mobile channels, Desktop for others)
|
|
2299
2771
|
setPreviewDevice(initialDevice);
|
|
2772
|
+
setActivePreviewTab(PREVIEW_TAB_RCS);
|
|
2300
2773
|
}
|
|
2301
2774
|
}, [show, initialDevice]);
|
|
2302
2775
|
|
|
@@ -2305,79 +2778,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
2305
2778
|
*/
|
|
2306
2779
|
useEffect(() => {
|
|
2307
2780
|
if (previewData) {
|
|
2308
|
-
setPreviewDataHtml(previewData);
|
|
2781
|
+
setPreviewDataHtml(toPlainPreviewData(previewData));
|
|
2309
2782
|
}
|
|
2310
2783
|
}, [previewData]);
|
|
2311
2784
|
|
|
2312
|
-
/**
|
|
2313
|
-
* Process extracted tags and categorize them
|
|
2314
|
-
*/
|
|
2315
|
-
useEffect(() => {
|
|
2316
|
-
// Categorize tags into required and optional
|
|
2317
|
-
const required = [];
|
|
2318
|
-
const optional = [];
|
|
2319
|
-
let hasPersonalizationTags = false;
|
|
2320
|
-
|
|
2321
|
-
if (extractedTags?.length > 0) {
|
|
2322
|
-
const processTag = (tag, parentPath = '') => {
|
|
2323
|
-
const currentPath = parentPath ? `${parentPath}.${tag.name}` : tag.name;
|
|
2324
|
-
|
|
2325
|
-
// Skip unsubscribe tag for input fields
|
|
2326
|
-
if (tag?.name === UNSUBSCRIBE_TAG_NAME) {
|
|
2327
|
-
return;
|
|
2328
|
-
}
|
|
2329
|
-
|
|
2330
|
-
hasPersonalizationTags = true;
|
|
2331
|
-
|
|
2332
|
-
if (tag?.metaData?.userDriven === false) {
|
|
2333
|
-
required.push({
|
|
2334
|
-
...tag,
|
|
2335
|
-
fullPath: currentPath,
|
|
2336
|
-
});
|
|
2337
|
-
} else if (tag?.metaData?.userDriven === true) {
|
|
2338
|
-
optional.push({
|
|
2339
|
-
...tag,
|
|
2340
|
-
fullPath: currentPath,
|
|
2341
|
-
});
|
|
2342
|
-
}
|
|
2343
|
-
|
|
2344
|
-
if (tag?.children?.length > 0) {
|
|
2345
|
-
tag.children.forEach((child) => processTag(child, currentPath));
|
|
2346
|
-
}
|
|
2347
|
-
};
|
|
2348
|
-
|
|
2349
|
-
extractedTags.forEach((tag) => processTag(tag));
|
|
2350
|
-
|
|
2351
|
-
if (hasPersonalizationTags) {
|
|
2352
|
-
setRequiredTags(required);
|
|
2353
|
-
setOptionalTags(optional);
|
|
2354
|
-
setTagsExtracted(true); // Mark tags as extracted and processed
|
|
2355
|
-
|
|
2356
|
-
// Initialize custom values for required tags
|
|
2357
|
-
const initialValues = {};
|
|
2358
|
-
required.forEach((tag) => {
|
|
2359
|
-
initialValues[tag?.fullPath] = '';
|
|
2360
|
-
});
|
|
2361
|
-
optional.forEach((tag) => {
|
|
2362
|
-
initialValues[tag?.fullPath] = '';
|
|
2363
|
-
});
|
|
2364
|
-
setCustomValues(initialValues);
|
|
2365
|
-
} else {
|
|
2366
|
-
// Reset all tag-related state if no personalization tags
|
|
2367
|
-
setRequiredTags([]);
|
|
2368
|
-
setOptionalTags([]);
|
|
2369
|
-
setCustomValues({});
|
|
2370
|
-
setTagsExtracted(false);
|
|
2371
|
-
}
|
|
2372
|
-
} else {
|
|
2373
|
-
// Reset all tag-related state if no tags
|
|
2374
|
-
setRequiredTags([]);
|
|
2375
|
-
setOptionalTags([]);
|
|
2376
|
-
setCustomValues({});
|
|
2377
|
-
setTagsExtracted(false);
|
|
2378
|
-
}
|
|
2379
|
-
}, [extractedTags]);
|
|
2380
|
-
|
|
2381
2785
|
/**
|
|
2382
2786
|
* Handle customer selection and fetch prefilled values
|
|
2383
2787
|
*/
|
|
@@ -2385,17 +2789,15 @@ const CommonTestAndPreview = (props) => {
|
|
|
2385
2789
|
if (selectedCustomer && config.enableCustomerSearch !== false) {
|
|
2386
2790
|
setTagsExtracted(true); // Auto-open custom values editor
|
|
2387
2791
|
|
|
2388
|
-
// Get all available tags
|
|
2389
|
-
const allTags = [...requiredTags, ...optionalTags];
|
|
2390
2792
|
const requiredTagObj = {};
|
|
2391
|
-
|
|
2793
|
+
allRequiredTags.forEach((tag) => {
|
|
2392
2794
|
requiredTagObj[tag?.fullPath] = '';
|
|
2393
2795
|
});
|
|
2394
2796
|
if (allTags.length > 0) {
|
|
2395
2797
|
const payload = preparePreviewPayload(
|
|
2396
|
-
|
|
2798
|
+
activeChannelForActions,
|
|
2397
2799
|
formData || {},
|
|
2398
|
-
|
|
2800
|
+
activeContentForActions,
|
|
2399
2801
|
{
|
|
2400
2802
|
...requiredTagObj,
|
|
2401
2803
|
},
|
|
@@ -2404,7 +2806,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2404
2806
|
actions.getPrefilledValuesRequested(payload);
|
|
2405
2807
|
}
|
|
2406
2808
|
}
|
|
2407
|
-
}, [selectedCustomer]);
|
|
2809
|
+
}, [selectedCustomer, allTags.length, activeChannelForActions, activePreviewTab]);
|
|
2408
2810
|
|
|
2409
2811
|
/**
|
|
2410
2812
|
* Update custom values with prefilled values from API
|
|
@@ -2415,7 +2817,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2415
2817
|
if (prefilledValues && selectedCustomer) {
|
|
2416
2818
|
// Always replace all values with prefilled values
|
|
2417
2819
|
const updatedValues = {};
|
|
2418
|
-
|
|
2820
|
+
allTags.forEach((tag) => {
|
|
2419
2821
|
updatedValues[tag?.fullPath] = prefilledValues[tag?.fullPath] || '';
|
|
2420
2822
|
});
|
|
2421
2823
|
|
|
@@ -2423,16 +2825,17 @@ const CommonTestAndPreview = (props) => {
|
|
|
2423
2825
|
|
|
2424
2826
|
// Update preview with prefilled values (this is a valid preview call trigger)
|
|
2425
2827
|
const payload = preparePreviewPayload(
|
|
2426
|
-
|
|
2828
|
+
activeChannelForActions,
|
|
2427
2829
|
formData || {},
|
|
2428
|
-
|
|
2830
|
+
activeContentForActions,
|
|
2429
2831
|
updatedValues,
|
|
2430
2832
|
selectedCustomer
|
|
2431
2833
|
);
|
|
2432
2834
|
actions.updatePreviewRequested(payload);
|
|
2433
2835
|
setHasPreviewCallBeenMade(true); // Mark that preview call was made
|
|
2836
|
+
void syncSmsFallbackPreview(updatedValues, selectedCustomer);
|
|
2434
2837
|
}
|
|
2435
|
-
}, [JSON.stringify(prefilledValues), selectedCustomer]);
|
|
2838
|
+
}, [JSON.stringify(prefilledValues), selectedCustomer, activeChannelForActions, activePreviewTab]);
|
|
2436
2839
|
|
|
2437
2840
|
/**
|
|
2438
2841
|
* Map channel constants to display names (lowercase for message)
|
|
@@ -2526,11 +2929,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2526
2929
|
setTagsExtracted(true); // Auto-open custom values editor
|
|
2527
2930
|
|
|
2528
2931
|
// Clear any existing values while waiting for prefilled values
|
|
2529
|
-
|
|
2530
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2531
|
-
emptyValues[tag?.fullPath] = '';
|
|
2532
|
-
});
|
|
2533
|
-
setCustomValues(emptyValues);
|
|
2932
|
+
setCustomValues(buildEmptyValues());
|
|
2534
2933
|
};
|
|
2535
2934
|
|
|
2536
2935
|
/**
|
|
@@ -2544,11 +2943,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2544
2943
|
actions.clearPreviewErrors();
|
|
2545
2944
|
|
|
2546
2945
|
// Initialize empty values for all tags
|
|
2547
|
-
|
|
2548
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2549
|
-
emptyValues[tag?.fullPath] = '';
|
|
2550
|
-
});
|
|
2551
|
-
setCustomValues(emptyValues);
|
|
2946
|
+
setCustomValues(buildEmptyValues());
|
|
2552
2947
|
|
|
2553
2948
|
// Don't make preview call when clearing selection - just reset to raw content
|
|
2554
2949
|
// Preview will be shown using raw formData/content
|
|
@@ -2584,17 +2979,17 @@ const CommonTestAndPreview = (props) => {
|
|
|
2584
2979
|
*/
|
|
2585
2980
|
const handleDiscardCustomValues = () => {
|
|
2586
2981
|
// Initialize empty values for all tags
|
|
2587
|
-
const emptyValues =
|
|
2588
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2589
|
-
emptyValues[tag?.fullPath] = '';
|
|
2590
|
-
});
|
|
2982
|
+
const emptyValues = buildEmptyValues();
|
|
2591
2983
|
setCustomValues(emptyValues);
|
|
2592
2984
|
|
|
2985
|
+
// Reset SMS fallback preview so it shows raw template (with {{tags}} visible) after discard
|
|
2986
|
+
setSmsFallbackPreviewText(undefined);
|
|
2987
|
+
|
|
2593
2988
|
// Update preview with empty values (this is a valid preview call trigger)
|
|
2594
2989
|
const payload = preparePreviewPayload(
|
|
2595
|
-
|
|
2990
|
+
activeChannelForActions,
|
|
2596
2991
|
formData || {},
|
|
2597
|
-
|
|
2992
|
+
activeContentForActions,
|
|
2598
2993
|
emptyValues,
|
|
2599
2994
|
selectedCustomer
|
|
2600
2995
|
);
|
|
@@ -2609,13 +3004,15 @@ const CommonTestAndPreview = (props) => {
|
|
|
2609
3004
|
const handleUpdatePreview = async () => {
|
|
2610
3005
|
try {
|
|
2611
3006
|
const payload = preparePreviewPayload(
|
|
2612
|
-
|
|
3007
|
+
activeChannelForActions,
|
|
2613
3008
|
formData || {},
|
|
2614
|
-
|
|
3009
|
+
activeContentForActions,
|
|
2615
3010
|
customValues,
|
|
2616
3011
|
selectedCustomer
|
|
2617
3012
|
);
|
|
2618
3013
|
await actions.updatePreviewRequested(payload);
|
|
3014
|
+
|
|
3015
|
+
await syncSmsFallbackPreview(customValues, selectedCustomer);
|
|
2619
3016
|
setHasPreviewCallBeenMade(true); // Mark that preview call was made
|
|
2620
3017
|
} catch (error) {
|
|
2621
3018
|
CapNotification.error({
|
|
@@ -2625,25 +3022,115 @@ const CommonTestAndPreview = (props) => {
|
|
|
2625
3022
|
};
|
|
2626
3023
|
|
|
2627
3024
|
/**
|
|
2628
|
-
*
|
|
3025
|
+
* Categorize extracted tags into required/optional.
|
|
2629
3026
|
*/
|
|
2630
|
-
const
|
|
2631
|
-
|
|
2632
|
-
|
|
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
|
+
};
|
|
2633
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.
|
|
3082
|
+
*/
|
|
3083
|
+
useEffect(() => {
|
|
3084
|
+
if (!show) return;
|
|
3085
|
+
const hasFallbackSmsTemplate = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
3086
|
+
if (channel === CHANNELS.RCS && hasFallbackSmsTemplate) {
|
|
3087
|
+
applyRcsSmsFallbackTagExtraction();
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
3090
|
+
const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
|
|
3091
|
+
let smsTagSource = channel === CHANNELS.SMS && (!extractedTags || extractedTags.length === 0)
|
|
3092
|
+
? buildSyntheticSmsMustacheTags(smsEditorBody)
|
|
3093
|
+
: (extractedTags ?? []);
|
|
3094
|
+
if (channel === CHANNELS.SMS && !smsTemplateHasMustacheTags(smsEditorBody)) {
|
|
3095
|
+
smsTagSource = [];
|
|
3096
|
+
}
|
|
3097
|
+
const { required, optional, hasPersonalizationTags } = categorizeTags(smsTagSource);
|
|
3098
|
+
setRequiredTags(required);
|
|
3099
|
+
setOptionalTags(optional);
|
|
3100
|
+
setTagsExtracted(hasPersonalizationTags);
|
|
3101
|
+
setCustomValues((prev) => mergeCustomValuesWithTagKeys(prev, { required, optional }, { required: [], optional: [] }));
|
|
3102
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- applyRcsSmsFallbackTagExtraction closes over latest extractedTags/smsFallbackExtractedTags
|
|
3103
|
+
}, [show, extractedTags, channel, smsFallbackContent, smsFallbackExtractedTags, getCurrentContent, smsFallbackTextForTagExtraction]);
|
|
3104
|
+
|
|
3105
|
+
/**
|
|
3106
|
+
* Get content to run tag extraction on (channel-specific).
|
|
3107
|
+
*/
|
|
3108
|
+
const getContentForTagExtraction = () => {
|
|
3109
|
+
let contentToExtract = activeContentForActions;
|
|
2634
3110
|
if (channel === CHANNELS.EMAIL && formData) {
|
|
2635
3111
|
const currentTabData = formData[currentTab - 1];
|
|
2636
3112
|
const activeTab = currentTabData?.activeTab;
|
|
2637
3113
|
const templateContent = currentTabData?.[activeTab]?.['template-content'];
|
|
2638
3114
|
contentToExtract = templateContent || contentToExtract;
|
|
2639
3115
|
}
|
|
3116
|
+
return contentToExtract;
|
|
3117
|
+
};
|
|
2640
3118
|
|
|
2641
|
-
|
|
3119
|
+
/**
|
|
3120
|
+
* Handle extract tags
|
|
3121
|
+
*/
|
|
3122
|
+
const handleExtractTags = () => {
|
|
3123
|
+
if (channel === CHANNELS.RCS) {
|
|
3124
|
+
applyRcsSmsFallbackTagExtraction();
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
const contentToExtract = getContentForTagExtraction();
|
|
2642
3129
|
const tags = contentToExtract.match(/{{[^}]+}}/g) || [];
|
|
2643
3130
|
const hasPersonalizationTags = tags.some((tag) => !tag.includes(UNSUBSCRIBE_TAG_NAME));
|
|
3131
|
+
const onlyUnsubscribe = !hasPersonalizationTags && tags.length === 1 && tags[0].includes(UNSUBSCRIBE_TAG_NAME);
|
|
2644
3132
|
|
|
2645
|
-
if (
|
|
2646
|
-
// If only unsubscribe tag is present, show noTagsExtracted message
|
|
3133
|
+
if (onlyUnsubscribe) {
|
|
2647
3134
|
setTagsExtracted(false);
|
|
2648
3135
|
setRequiredTags([]);
|
|
2649
3136
|
setOptionalTags([]);
|
|
@@ -2651,10 +3138,9 @@ const CommonTestAndPreview = (props) => {
|
|
|
2651
3138
|
return;
|
|
2652
3139
|
}
|
|
2653
3140
|
|
|
2654
|
-
// Extract tags
|
|
2655
3141
|
setTagsExtracted(true);
|
|
2656
3142
|
const { templateSubject, templateContent } = prepareTagExtractionPayload(
|
|
2657
|
-
|
|
3143
|
+
activeChannelForActions,
|
|
2658
3144
|
formData || {},
|
|
2659
3145
|
contentToExtract
|
|
2660
3146
|
);
|
|
@@ -2716,7 +3202,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2716
3202
|
if (existingTestCustomer) {
|
|
2717
3203
|
const entityId = existingTestCustomer.userId ?? existingTestCustomer.customerId;
|
|
2718
3204
|
if (entityId != null) {
|
|
2719
|
-
const id =
|
|
3205
|
+
const id = normalizeTestEntityId(entityId);
|
|
2720
3206
|
setSelectedTestEntities((prev) => (
|
|
2721
3207
|
prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
|
|
2722
3208
|
));
|
|
@@ -2753,7 +3239,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2753
3239
|
(c) => String(c?.customerId) === customerIdFromLookup || String(c?.userId) === customerIdFromLookup
|
|
2754
3240
|
);
|
|
2755
3241
|
if (alreadyInTestListByCustomerId) {
|
|
2756
|
-
const id =
|
|
3242
|
+
const id = normalizeTestEntityId(customerIdFromLookup);
|
|
2757
3243
|
setSelectedTestEntities((prev) => (
|
|
2758
3244
|
prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
|
|
2759
3245
|
));
|
|
@@ -2790,26 +3276,21 @@ const CommonTestAndPreview = (props) => {
|
|
|
2790
3276
|
const handleSendTestMessage = () => {
|
|
2791
3277
|
const allUserIds = [];
|
|
2792
3278
|
selectedTestEntities.forEach((entityId) => {
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
if (group) {
|
|
2797
|
-
allUserIds.push(...group.userIds);
|
|
2798
|
-
}
|
|
3279
|
+
const group = testGroups.find((g) => testEntityIdsEqual(g.groupId, entityId));
|
|
3280
|
+
if (group) {
|
|
3281
|
+
allUserIds.push(...group.userIds);
|
|
2799
3282
|
} else {
|
|
2800
|
-
|
|
2801
|
-
? String(entityId).slice('customer:'.length)
|
|
2802
|
-
: String(entityId);
|
|
2803
|
-
allUserIds.push(Number(rawId));
|
|
3283
|
+
allUserIds.push(entityId);
|
|
2804
3284
|
}
|
|
2805
3285
|
});
|
|
2806
3286
|
const uniqueUserIds = [...new Set(allUserIds)];
|
|
2807
3287
|
|
|
2808
|
-
const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP].includes(channel)
|
|
3288
|
+
const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS].includes(channel)
|
|
2809
3289
|
? testPreviewDeliverySettings[channel]
|
|
2810
3290
|
: null;
|
|
2811
3291
|
|
|
2812
|
-
//
|
|
3292
|
+
// createMessageMeta must match the creative channel and full creative (RCS + SMS fallback in one meta).
|
|
3293
|
+
// Do not use activeChannelForActions / activeContentForActions — those follow the RCS vs Fallback SMS *preview* tab.
|
|
2813
3294
|
const initialPayload = prepareTestMessagePayload(
|
|
2814
3295
|
channel,
|
|
2815
3296
|
formData || content || {},
|
|
@@ -2817,7 +3298,8 @@ const CommonTestAndPreview = (props) => {
|
|
|
2817
3298
|
customValues,
|
|
2818
3299
|
uniqueUserIds,
|
|
2819
3300
|
previewData,
|
|
2820
|
-
deliveryOverride
|
|
3301
|
+
deliveryOverride,
|
|
3302
|
+
{},
|
|
2821
3303
|
);
|
|
2822
3304
|
|
|
2823
3305
|
actions.createMessageMetaRequested(
|
|
@@ -2850,11 +3332,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
2850
3332
|
// ============================================
|
|
2851
3333
|
// RENDER HELPER FUNCTIONS
|
|
2852
3334
|
// ============================================
|
|
2853
|
-
|
|
2854
3335
|
const renderLeftPanelContent = () => (
|
|
2855
3336
|
<LeftPanelContent
|
|
2856
|
-
isExtractingTags={isExtractingTags}
|
|
2857
|
-
extractedTags={
|
|
3337
|
+
isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
|
|
3338
|
+
extractedTags={leftPanelExtractedTags}
|
|
2858
3339
|
selectedCustomer={selectedCustomer}
|
|
2859
3340
|
handleCustomerSelect={handleCustomerSelect}
|
|
2860
3341
|
handleSearchCustomer={handleSearchCustomer}
|
|
@@ -2872,15 +3353,29 @@ const CommonTestAndPreview = (props) => {
|
|
|
2872
3353
|
|
|
2873
3354
|
const renderCustomValuesEditor = () => (
|
|
2874
3355
|
<CustomValuesEditor
|
|
2875
|
-
isExtractingTags={isExtractingTags}
|
|
3356
|
+
isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
|
|
2876
3357
|
isUpdatePreviewDisabled={isUpdatePreviewDisabled}
|
|
2877
3358
|
showJSON={showJSON}
|
|
2878
3359
|
setShowJSON={setShowJSON}
|
|
2879
3360
|
customValues={customValues}
|
|
2880
3361
|
handleJSONTextChange={handleJSONTextChange}
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
3362
|
+
sections={[
|
|
3363
|
+
{
|
|
3364
|
+
key: channel,
|
|
3365
|
+
title:
|
|
3366
|
+
channel === CHANNELS.RCS
|
|
3367
|
+
? messages.rcsTagsSectionTitle
|
|
3368
|
+
: messages[`${channel}TagsSectionTitle`],
|
|
3369
|
+
requiredTags,
|
|
3370
|
+
optionalTags,
|
|
3371
|
+
},
|
|
3372
|
+
{
|
|
3373
|
+
key: PREVIEW_TAB_SMS_FALLBACK,
|
|
3374
|
+
title: channel === CHANNELS.RCS && smsFallbackContent?.templateContent ? messages.smsFallbackTagsSectionTitle : null,
|
|
3375
|
+
requiredTags: smsFallbackRequiredTags,
|
|
3376
|
+
optionalTags: smsFallbackOptionalTags,
|
|
3377
|
+
},
|
|
3378
|
+
]}
|
|
2884
3379
|
handleCustomValueChange={handleCustomValueChange}
|
|
2885
3380
|
handleDiscardCustomValues={handleDiscardCustomValues}
|
|
2886
3381
|
handleUpdatePreview={handleUpdatePreview}
|
|
@@ -2896,18 +3391,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
2896
3391
|
}));
|
|
2897
3392
|
};
|
|
2898
3393
|
|
|
2899
|
-
/** Trim pasted emails (trailing CR/LF).
|
|
3394
|
+
/** Trim pasted emails (trailing CR/LF). Allow any input for SMS; valid-only check is in renderAddTestCustomerButton. */
|
|
2900
3395
|
const handleTestCustomersSearch = useCallback((value) => {
|
|
2901
3396
|
if (value == null || value === '') {
|
|
2902
3397
|
setSearchValue('');
|
|
2903
3398
|
return;
|
|
2904
3399
|
}
|
|
2905
|
-
|
|
2906
|
-
if (channel === CHANNELS.SMS) {
|
|
2907
|
-
setSearchValue(formatPhoneNumber(raw));
|
|
2908
|
-
} else {
|
|
2909
|
-
setSearchValue(raw);
|
|
2910
|
-
}
|
|
3400
|
+
setSearchValue(String(value).trim());
|
|
2911
3401
|
}, [channel]);
|
|
2912
3402
|
|
|
2913
3403
|
const renderSendTestMessage = () => (
|
|
@@ -2925,12 +3415,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
2925
3415
|
renderAddTestCustomerButton={renderAddTestCustomerButton}
|
|
2926
3416
|
formatMessage={formatMessage}
|
|
2927
3417
|
deliverySettings={testPreviewDeliverySettings[channel]}
|
|
2928
|
-
|
|
3418
|
+
senderDetailsByChannel={senderDetailsByChannel}
|
|
2929
3419
|
wecrmAccounts={wecrmAccounts}
|
|
2930
3420
|
onSaveDeliverySettings={handleSaveDeliverySettings}
|
|
2931
3421
|
isLoadingSenderDetails={isLoadingSenderDetails}
|
|
2932
3422
|
smsTraiDltEnabled={smsTraiDltEnabled}
|
|
2933
3423
|
registeredSenderIds={registeredSenderIds}
|
|
3424
|
+
isChannelSmsFallbackPreviewEnabled={channel === CHANNELS.RCS && !!smsFallbackContent?.templateContent}
|
|
2934
3425
|
searchValue={searchValue}
|
|
2935
3426
|
setSearchValue={handleTestCustomersSearch}
|
|
2936
3427
|
/>
|
|
@@ -2944,14 +3435,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
2944
3435
|
|
|
2945
3436
|
const renderAddTestCustomerButton = () => {
|
|
2946
3437
|
const raw = (searchValue || '').trim();
|
|
2947
|
-
const value = channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw;
|
|
2948
3438
|
const showAddButton =
|
|
2949
3439
|
[CHANNELS.EMAIL, CHANNELS.SMS].includes(channel) &&
|
|
2950
|
-
(channel === CHANNELS.EMAIL ? isValidEmail(
|
|
3440
|
+
(channel === CHANNELS.EMAIL ? isValidEmail(raw) : isValidMobile(formatPhoneNumber(raw)));
|
|
2951
3441
|
if (!showAddButton) return null;
|
|
2952
3442
|
return (
|
|
2953
3443
|
<AddTestCustomerButton
|
|
2954
|
-
searchValue={
|
|
3444
|
+
searchValue={channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw}
|
|
2955
3445
|
handleAddTestCustomer={handleAddTestCustomer}
|
|
2956
3446
|
/>
|
|
2957
3447
|
);
|