@capillarytech/creatives-library 8.0.359-alpha.0 → 8.0.359-alpha.1
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/index.html +1 -0
- package/package.json +1 -1
- package/services/tests/api.test.js +35 -20
- package/utils/cdnTransformation.js +75 -3
- package/utils/commonUtils.js +19 -1
- package/utils/rcsPayloadUtils.js +92 -0
- package/utils/templateVarUtils.js +201 -0
- package/utils/tests/cdnTransformation.test.js +127 -0
- package/utils/tests/rcsPayloadUtils.test.js +226 -0
- package/utils/tests/templateVarUtils.test.js +204 -0
- package/v2Components/CapActionButton/constants.js +7 -0
- package/v2Components/CapActionButton/index.js +166 -108
- 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/CapImageUpload/index.js +2 -2
- package/v2Components/CapTagList/index.js +10 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +72 -49
- 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 +79 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +16 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +157 -15
- package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +14 -132
- package/v2Components/CommonTestAndPreview/UnifiedPreview/WebPushPreviewContent.js +169 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +400 -239
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +202 -10
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
- package/v2Components/CommonTestAndPreview/constants.js +40 -2
- package/v2Components/CommonTestAndPreview/index.js +887 -453
- 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/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/PreviewHeader.test.js +163 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +0 -364
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WebPushPreviewContent.test.js +522 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +454 -1
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +2 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +327 -4
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
- package/v2Components/FormBuilder/index.js +167 -56
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
- package/v2Components/SmsFallback/constants.js +73 -0
- package/v2Components/SmsFallback/index.js +956 -0
- package/v2Components/SmsFallback/index.scss +265 -0
- package/v2Components/SmsFallback/messages.js +78 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +119 -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 +223 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +37 -22
- package/v2Components/TemplatePreview/constants.js +2 -0
- package/v2Components/TemplatePreview/index.js +143 -31
- package/v2Components/TemplatePreview/tests/index.test.js +142 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +15 -3
- 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/App/constants.js +3 -0
- package/v2Containers/App/tests/constants.test.js +61 -0
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +17 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
- package/v2Containers/CreativesContainer/constants.js +9 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
- package/v2Containers/CreativesContainer/index.js +382 -127
- package/v2Containers/CreativesContainer/index.scss +83 -1
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +79 -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 -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/constants.js +120 -11
- package/v2Containers/Rcs/index.js +2577 -812
- package/v2Containers/Rcs/index.scss +281 -8
- package/v2Containers/Rcs/messages.js +34 -3
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98036 -70145
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
- 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 +106 -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 +640 -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/TemplatesActionBar.js +101 -0
- package/v2Containers/Templates/_templates.scss +166 -86
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +203 -145
- package/v2Containers/Templates/sagas.js +62 -13
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1062 -1017
- package/v2Containers/Templates/tests/sagas.test.js +222 -22
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
- package/v2Containers/Templates/tests/webpush.test.js +375 -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/Viber/constants.js +0 -19
- package/v2Containers/Viber/index.js +47 -714
- package/v2Containers/Viber/index.scss +0 -148
- package/v2Containers/Viber/messages.js +0 -116
- package/v2Containers/Viber/tests/index.test.js +0 -80
- package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
- package/v2Containers/WebPush/Create/index.js +91 -8
- package/v2Containers/WebPush/Create/index.scss +7 -0
- package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +348 -0
- package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +325 -0
- package/v2Containers/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
|
@@ -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,24 @@ 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
|
+
DAYS,
|
|
89
|
+
RCS_TEST_META_CONTENT_TYPE_RICHCARD,
|
|
90
|
+
RCS_TEST_META_CARD_TYPE_STANDALONE,
|
|
91
|
+
RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
|
|
92
|
+
RCS_TEST_META_CARD_WIDTH_SMALL,
|
|
93
|
+
SMS_MUSTACHE_TAG_PATTERN,
|
|
85
94
|
} from './constants';
|
|
86
|
-
|
|
87
|
-
// Import utilities
|
|
88
95
|
import { getCdnUrl } from '../../utils/cdnTransformation';
|
|
96
|
+
import {
|
|
97
|
+
normalizePreviewApiPayload,
|
|
98
|
+
extractPreviewFromLiquidResponse,
|
|
99
|
+
getSmsFallbackTextForTagExtraction,
|
|
100
|
+
} from './previewApiUtils';
|
|
101
|
+
import { pickFirstSmsFallbackTemplateString } from '../../v2Containers/Rcs/rcsLibraryHydrationUtils';
|
|
102
|
+
|
|
89
103
|
import { isValidEmail, isValidMobile, formatPhoneNumber } from '../../utils/commonUtils';
|
|
90
104
|
import { getMembersLookup } from '../../services/api';
|
|
91
105
|
|
|
@@ -108,6 +122,85 @@ const filterUsableGsmSendersForDomain = (domain, gsmSenders, { skipDomainNameEch
|
|
|
108
122
|
});
|
|
109
123
|
};
|
|
110
124
|
|
|
125
|
+
/** Preview payload from Redux may be an Immutable Map — normalize for React state. */
|
|
126
|
+
const toPlainPreviewData = (data) => {
|
|
127
|
+
if (data == null) return null;
|
|
128
|
+
const plain = typeof data.toJS === 'function' ? data.toJS() : data;
|
|
129
|
+
return normalizePreviewApiPayload(plain);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Merge existing customValues with tag keys from categorized groups.
|
|
134
|
+
* Each group is { required, optional } (arrays of tag objects with fullPath).
|
|
135
|
+
* Preserves existing values; adds '' for any tag key not yet present.
|
|
136
|
+
* Reusable for RCS+fallback and any flow that needs to ensure customValues has keys for tags.
|
|
137
|
+
*/
|
|
138
|
+
const mergeCustomValuesWithTagKeys = (prev, ...categorizedGroups) => {
|
|
139
|
+
const next = { ...(prev || {}) };
|
|
140
|
+
categorizedGroups.forEach((group) => {
|
|
141
|
+
[...(group.required || []), ...(group.optional || [])].forEach((tag) => {
|
|
142
|
+
const key = tag?.fullPath;
|
|
143
|
+
if (key && next[key] === undefined) next[key] = '';
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
return next;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** True when `body` contains `{{name}}` mustache tokens (user-fillable personalization tags).
|
|
150
|
+
* DLT `{#name#}` slots are pre-bound template variables and are intentionally excluded. */
|
|
151
|
+
const smsTemplateHasMustacheTags = (body) =>
|
|
152
|
+
typeof body === 'string' && SMS_MUSTACHE_TAG_PATTERN.test(body);
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Build tag rows from `{{…}}` mustache tokens only — DLT `{#…#}` slots are excluded because
|
|
156
|
+
* they are pre-bound template variables, not user-fillable personalization tags.
|
|
157
|
+
* Passing a mustache-only captureRegex to extractTemplateVariables skips the DLT branch.
|
|
158
|
+
* A non-global regex is used so ensureGlobalRegexForExecLoop creates a fresh instance on each call.
|
|
159
|
+
*/
|
|
160
|
+
const buildSyntheticSmsMustacheTags = (body = '') => {
|
|
161
|
+
if (!body || typeof body !== 'string') return [];
|
|
162
|
+
return extractTemplateVariables(body, /\{\{([^}]+)\}\}/).map((name) => ({
|
|
163
|
+
name,
|
|
164
|
+
metaData: { userDriven: false },
|
|
165
|
+
children: [],
|
|
166
|
+
}));
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/** RCS createMessageMeta: media shape (mediaUrl, thumbnailUrl, height string). */
|
|
170
|
+
const normalizeRcsTestCardMedia = (media) => {
|
|
171
|
+
if (!media || typeof media !== 'object') return undefined;
|
|
172
|
+
const mediaUrl =
|
|
173
|
+
media.mediaUrl != null && String(media.mediaUrl).trim() !== ''
|
|
174
|
+
? String(media.mediaUrl)
|
|
175
|
+
: media.url != null && String(media.url).trim() !== ''
|
|
176
|
+
? String(media.url)
|
|
177
|
+
: '';
|
|
178
|
+
const thumbnailUrl = media.thumbnailUrl != null ? String(media.thumbnailUrl) : '';
|
|
179
|
+
const height = media.height != null ? String(media.height) : undefined;
|
|
180
|
+
const out = { mediaUrl, thumbnailUrl };
|
|
181
|
+
if (height) out.height = height;
|
|
182
|
+
return out;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/** RCS createMessageMeta: suggestion shape (index, type, text, phoneNumber, url, postback). */
|
|
186
|
+
const mapRcsSuggestionForTestMeta = (suggestionRow, index) => ({
|
|
187
|
+
index,
|
|
188
|
+
type: suggestionRow?.type ?? '',
|
|
189
|
+
text: suggestionRow?.text != null ? String(suggestionRow.text) : '',
|
|
190
|
+
phoneNumber:
|
|
191
|
+
suggestionRow?.phoneNumber != null
|
|
192
|
+
? String(suggestionRow.phoneNumber)
|
|
193
|
+
: suggestionRow?.phone_number != null
|
|
194
|
+
? String(suggestionRow.phone_number)
|
|
195
|
+
: '',
|
|
196
|
+
url: suggestionRow?.url !== undefined ? suggestionRow.url : null,
|
|
197
|
+
postback:
|
|
198
|
+
suggestionRow?.postback != null
|
|
199
|
+
? String(suggestionRow.postback)
|
|
200
|
+
: suggestionRow?.text != null
|
|
201
|
+
? String(suggestionRow.text)
|
|
202
|
+
: '',
|
|
203
|
+
});
|
|
111
204
|
|
|
112
205
|
/**
|
|
113
206
|
* CapTreeSelect and group resolution use strict equality; API data may mix numeric and string ids.
|
|
@@ -133,7 +226,7 @@ const testEntityIdsEqual = (a, b) => {
|
|
|
133
226
|
*/
|
|
134
227
|
const CommonTestAndPreview = (props) => {
|
|
135
228
|
const {
|
|
136
|
-
intl: { formatMessage },
|
|
229
|
+
intl: { formatMessage, locale: userLocale = 'en' },
|
|
137
230
|
show,
|
|
138
231
|
onClose,
|
|
139
232
|
channel, // The channel: 'EMAIL', 'SMS', 'RCS', etc.
|
|
@@ -169,13 +262,23 @@ const CommonTestAndPreview = (props) => {
|
|
|
169
262
|
...additionalProps
|
|
170
263
|
} = props;
|
|
171
264
|
|
|
265
|
+
const smsFallbackContent = additionalProps?.smsFallbackContent;
|
|
266
|
+
const smsFallbackTextForTagExtraction = useMemo(
|
|
267
|
+
() => getSmsFallbackTextForTagExtraction(smsFallbackContent),
|
|
268
|
+
[smsFallbackContent],
|
|
269
|
+
);
|
|
172
270
|
// ============================================
|
|
173
271
|
// STATE MANAGEMENT
|
|
174
272
|
// ============================================
|
|
175
273
|
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
|
176
274
|
const [requiredTags, setRequiredTags] = useState([]);
|
|
177
275
|
const [optionalTags, setOptionalTags] = useState([]);
|
|
276
|
+
const [smsFallbackExtractedTags, setSmsFallbackExtractedTags] = useState([]);
|
|
277
|
+
const [smsFallbackRequiredTags, setSmsFallbackRequiredTags] = useState([]);
|
|
278
|
+
const [smsFallbackOptionalTags, setSmsFallbackOptionalTags] = useState([]);
|
|
279
|
+
const [isExtractingSmsFallbackTags, setIsExtractingSmsFallbackTags] = useState(false);
|
|
178
280
|
const [customValues, setCustomValues] = useState({});
|
|
281
|
+
const previewCustomValuesRef = useRef({});
|
|
179
282
|
const [showJSON, setShowJSON] = useState(false);
|
|
180
283
|
const [tagsExtracted, setTagsExtracted] = useState(false);
|
|
181
284
|
|
|
@@ -186,10 +289,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
186
289
|
const [customerData, setCustomerData] = useState({ name: '', email: '', mobile: '', customerId: '' });
|
|
187
290
|
|
|
188
291
|
const [previewDevice, setPreviewDevice] = useState(initialDevice);
|
|
292
|
+
const [activePreviewTab, setActivePreviewTab] = useState(PREVIEW_TAB_RCS);
|
|
293
|
+
const [smsFallbackPreviewText, setSmsFallbackPreviewText] = useState(undefined);
|
|
189
294
|
// Track if a preview call has been made (to know when to use previewDataHtml vs raw content)
|
|
190
295
|
const [hasPreviewCallBeenMade, setHasPreviewCallBeenMade] = useState(false);
|
|
191
|
-
// Viber: tag values applied to preview only after explicit Update preview / Discard (not while typing)
|
|
192
|
-
const [viberPreviewTagValues, setViberPreviewTagValues] = useState(null);
|
|
193
296
|
const [previewDataHtml, setPreviewDataHtml] = useState(() => {
|
|
194
297
|
// Initialize preview data based on channel
|
|
195
298
|
if (channel === CHANNELS.EMAIL && formData) {
|
|
@@ -220,15 +323,22 @@ const CommonTestAndPreview = (props) => {
|
|
|
220
323
|
[CHANNELS.WHATSAPP]: {
|
|
221
324
|
domainId: null, senderMobNum: '', sourceAccountIdentifier: '',
|
|
222
325
|
},
|
|
326
|
+
[CHANNELS.RCS]: {
|
|
327
|
+
domainId: null,
|
|
328
|
+
domainGatewayMapId: null,
|
|
329
|
+
gsmSenderId: '',
|
|
330
|
+
smsFallbackDomainId: null,
|
|
331
|
+
cdmaSenderId: '', // gsmSenderId = RCS sender (domainId|senderId), cdmaSenderId = SMS fallback
|
|
332
|
+
},
|
|
223
333
|
});
|
|
224
334
|
|
|
225
|
-
const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP];
|
|
335
|
+
const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS];
|
|
226
336
|
const formDataForSendTest = formData ?? (content && typeof content === 'object' && !Array.isArray(content) ? content : formData);
|
|
227
337
|
const smsTemplateConfigs = formDataForSendTest?.templateConfigs || {};
|
|
228
338
|
const smsTraiDltEnabled = !!smsTemplateConfigs?.traiDltEnabled;
|
|
229
339
|
const registeredSenderIds = smsTemplateConfigs?.registeredSenderIds || [];
|
|
230
340
|
|
|
231
|
-
// Fetch sender details and WeCRM accounts when Test & Preview opens
|
|
341
|
+
// Fetch sender details and WeCRM accounts when Test & Preview opens (SMS, Email, WhatsApp, RCS — same process)
|
|
232
342
|
useEffect(() => {
|
|
233
343
|
if (!show || !channel) {
|
|
234
344
|
return;
|
|
@@ -237,39 +347,99 @@ const CommonTestAndPreview = (props) => {
|
|
|
237
347
|
if (actions.getSenderDetailsRequested) {
|
|
238
348
|
actions.getSenderDetailsRequested({ channel, orgUnitId: orgUnitId ?? -1 });
|
|
239
349
|
}
|
|
350
|
+
// SMS domains/senders are needed for RCS delivery UI (fallback row + slidebox) whenever RCS is open — not only when fallback body exists.
|
|
351
|
+
if (channel === CHANNELS.RCS && actions.getSenderDetailsRequested) {
|
|
352
|
+
actions.getSenderDetailsRequested({ channel: CHANNELS.SMS, orgUnitId: orgUnitId ?? -1 });
|
|
353
|
+
}
|
|
240
354
|
if (channel === CHANNELS.WHATSAPP && actions.getWeCrmAccountsRequested) {
|
|
241
355
|
actions.getWeCrmAccountsRequested({ sourceName: CHANNELS.WHATSAPP });
|
|
242
356
|
}
|
|
243
357
|
}
|
|
244
358
|
}, [show, channel, orgUnitId, actions]);
|
|
245
359
|
|
|
246
|
-
useEffect(() => {
|
|
247
|
-
if (!show) {
|
|
248
|
-
setCustomerModal([false, '']);
|
|
249
|
-
setSearchValue('');
|
|
250
|
-
setCustomerData({ name: '', email: '', mobile: '', customerId: '' });
|
|
251
|
-
}
|
|
252
|
-
}, [show]);
|
|
253
|
-
|
|
254
360
|
const findDefault = (arr) => (arr && arr.find((x) => x.default)) || (arr && arr[0]) || {};
|
|
255
361
|
|
|
256
362
|
// Auto-set default delivery setting when sender details load (campaigns-style: first domain + default/first sender)
|
|
257
363
|
useEffect(() => {
|
|
258
364
|
if (!channel || !channelsWithDeliverySettings.includes(channel)) return;
|
|
365
|
+
|
|
366
|
+
if (channel === CHANNELS.RCS) {
|
|
367
|
+
const rcsDomainRows = senderDetailsByChannel?.[CHANNELS.RCS] || [];
|
|
368
|
+
const smsFallbackDomainRows = senderDetailsByChannel?.[CHANNELS.SMS] || [];
|
|
369
|
+
if (!rcsDomainRows.length) return;
|
|
370
|
+
|
|
371
|
+
const currentRcsDeliverySettings = testPreviewDeliverySettings?.[CHANNELS.RCS] || {};
|
|
372
|
+
const isRcsGsmSenderUnset = !currentRcsDeliverySettings?.gsmSenderId;
|
|
373
|
+
const isSmsFallbackSenderUnset = !currentRcsDeliverySettings?.cdmaSenderId;
|
|
374
|
+
const isRcsDeliveryFullyUnset = isRcsGsmSenderUnset && isSmsFallbackSenderUnset;
|
|
375
|
+
const shouldOnlyFillSmsFallbackSender =
|
|
376
|
+
!isRcsGsmSenderUnset && isSmsFallbackSenderUnset && smsFallbackDomainRows.length > 0;
|
|
377
|
+
|
|
378
|
+
if (!isRcsDeliveryFullyUnset && !shouldOnlyFillSmsFallbackSender) return;
|
|
379
|
+
|
|
380
|
+
const firstRcsDomain = rcsDomainRows[0];
|
|
381
|
+
const firstSmsFallbackDomain = smsFallbackDomainRows[0];
|
|
382
|
+
const usableRcsGsmSenders = filterUsableGsmSendersForDomain(
|
|
383
|
+
firstRcsDomain,
|
|
384
|
+
firstRcsDomain?.gsmSenders,
|
|
385
|
+
{ skipDomainNameEchoFilter: true },
|
|
386
|
+
);
|
|
387
|
+
const usableSmsFallbackGsmSenders = firstSmsFallbackDomain
|
|
388
|
+
? filterUsableGsmSendersForDomain(firstSmsFallbackDomain, firstSmsFallbackDomain?.gsmSenders)
|
|
389
|
+
: [];
|
|
390
|
+
const defaultRcsGsmSender = usableRcsGsmSenders[0];
|
|
391
|
+
const defaultSmsFallbackGsmSender = usableSmsFallbackGsmSenders[0];
|
|
392
|
+
const rcsSenderCompositeValue =
|
|
393
|
+
firstRcsDomain?.domainId != null && defaultRcsGsmSender?.value != null
|
|
394
|
+
? `${firstRcsDomain.domainId}|${defaultRcsGsmSender.value}`
|
|
395
|
+
: (defaultRcsGsmSender?.value || '');
|
|
396
|
+
const smsFallbackSenderCompositeValue =
|
|
397
|
+
firstSmsFallbackDomain?.domainId != null && defaultSmsFallbackGsmSender?.value != null
|
|
398
|
+
? `${firstSmsFallbackDomain.domainId}|${defaultSmsFallbackGsmSender.value}`
|
|
399
|
+
: (defaultSmsFallbackGsmSender?.value || '');
|
|
400
|
+
|
|
401
|
+
setTestPreviewDeliverySettings((prev) => {
|
|
402
|
+
const previousRcsSettings = prev?.[CHANNELS.RCS] || {};
|
|
403
|
+
if (shouldOnlyFillSmsFallbackSender) {
|
|
404
|
+
if (!smsFallbackSenderCompositeValue) return prev;
|
|
405
|
+
return {
|
|
406
|
+
...prev,
|
|
407
|
+
[CHANNELS.RCS]: {
|
|
408
|
+
...previousRcsSettings,
|
|
409
|
+
smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
|
|
410
|
+
cdmaSenderId: smsFallbackSenderCompositeValue,
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
...prev,
|
|
416
|
+
[CHANNELS.RCS]: {
|
|
417
|
+
domainId: firstRcsDomain?.domainId ?? null,
|
|
418
|
+
domainGatewayMapId: firstRcsDomain?.dgmId ?? null,
|
|
419
|
+
gsmSenderId: rcsSenderCompositeValue,
|
|
420
|
+
smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
|
|
421
|
+
cdmaSenderId: smsFallbackSenderCompositeValue,
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
});
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
259
428
|
const domains = senderDetailsByChannel[channel];
|
|
260
429
|
if (!domains || domains.length === 0) return;
|
|
261
430
|
const {
|
|
262
|
-
domainId = '', gsmSenderId = '', senderEmail = '', senderMobNum = '',
|
|
431
|
+
domainId = '', gsmSenderId = '', cdmaSenderId = '', senderEmail = '', senderMobNum = '',
|
|
263
432
|
} = testPreviewDeliverySettings[channel] || {};
|
|
264
|
-
const isEmptySelection = !domainId && !gsmSenderId && !senderEmail && !senderMobNum;
|
|
433
|
+
const isEmptySelection = !domainId && !gsmSenderId && !cdmaSenderId && !senderEmail && !senderMobNum;
|
|
265
434
|
if (!isEmptySelection) return;
|
|
266
435
|
|
|
267
436
|
const whatsappAccountFromForm = channel === CHANNELS.WHATSAPP ? formData?.accountName : undefined;
|
|
268
437
|
const matchedWhatsappAccount = whatsappAccountFromForm
|
|
269
438
|
? (wecrmAccounts || []).find((account) => account?.name === whatsappAccountFromForm)
|
|
270
439
|
: null;
|
|
271
|
-
const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled
|
|
272
|
-
? domains.filter((domain) => (domain
|
|
440
|
+
const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled && registeredSenderIds?.length
|
|
441
|
+
? domains.filter((domain) => (domain?.gsmSenders || []).some((gsm) =>
|
|
442
|
+
registeredSenderIds?.includes(gsm?.value)))
|
|
273
443
|
: domains;
|
|
274
444
|
const [defaultDomain] = domains;
|
|
275
445
|
const [firstSmsDomain] = smsDomains;
|
|
@@ -284,8 +454,8 @@ const CommonTestAndPreview = (props) => {
|
|
|
284
454
|
const next = { ...prev };
|
|
285
455
|
if (channel === CHANNELS.SMS) {
|
|
286
456
|
const smsGsmSenders = smsTraiDltEnabled
|
|
287
|
-
? (firstDomain
|
|
288
|
-
: firstDomain
|
|
457
|
+
? (firstDomain?.gsmSenders || []).filter((gsm) => registeredSenderIds?.includes(gsm?.value))
|
|
458
|
+
: firstDomain?.gsmSenders;
|
|
289
459
|
next[channel] = {
|
|
290
460
|
domainId: firstDomain.domainId,
|
|
291
461
|
domainGatewayMapId: firstDomain.dgmId,
|
|
@@ -318,10 +488,29 @@ const CommonTestAndPreview = (props) => {
|
|
|
318
488
|
// MEMOIZED VALUES
|
|
319
489
|
// ============================================
|
|
320
490
|
|
|
491
|
+
const allTags = useMemo(
|
|
492
|
+
() => [...requiredTags, ...optionalTags, ...smsFallbackRequiredTags, ...smsFallbackOptionalTags],
|
|
493
|
+
[requiredTags, optionalTags, smsFallbackRequiredTags, smsFallbackOptionalTags]
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
const allRequiredTags = useMemo(
|
|
497
|
+
() => [...requiredTags, ...smsFallbackRequiredTags],
|
|
498
|
+
[requiredTags, smsFallbackRequiredTags]
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
const buildEmptyValues = useCallback(
|
|
502
|
+
() => allTags.reduce((acc, tag) => {
|
|
503
|
+
const key = tag?.fullPath;
|
|
504
|
+
if (key) acc[key] = '';
|
|
505
|
+
return acc;
|
|
506
|
+
}, {}),
|
|
507
|
+
[allTags]
|
|
508
|
+
);
|
|
509
|
+
|
|
321
510
|
// Check if update preview button should be disabled
|
|
322
511
|
const isUpdatePreviewDisabled = useMemo(() => (
|
|
323
|
-
|
|
324
|
-
), [
|
|
512
|
+
allRequiredTags.some((tag) => !customValues[tag.fullPath])
|
|
513
|
+
), [allRequiredTags, customValues]);
|
|
325
514
|
|
|
326
515
|
// Get current content based on channel and editor type
|
|
327
516
|
const getCurrentContent = useMemo(() => {
|
|
@@ -365,6 +554,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
365
554
|
return currentTabData.base['sms-editor'];
|
|
366
555
|
}
|
|
367
556
|
}
|
|
557
|
+
// DLT / Test & Preview shape: { templateConfigs: { template, templateId, ... } }
|
|
558
|
+
if (formData.templateConfigs?.template) {
|
|
559
|
+
const smsDltTemplateValue = formData.templateConfigs.template;
|
|
560
|
+
if (typeof smsDltTemplateValue === 'string') return smsDltTemplateValue;
|
|
561
|
+
if (Array.isArray(smsDltTemplateValue)) return smsDltTemplateValue.join('');
|
|
562
|
+
return '';
|
|
563
|
+
}
|
|
368
564
|
}
|
|
369
565
|
|
|
370
566
|
// SMS channel fallback - if formData is not available, use content directly
|
|
@@ -410,7 +606,70 @@ const CommonTestAndPreview = (props) => {
|
|
|
410
606
|
return content || '';
|
|
411
607
|
}, [channel, formData, currentTab, beeContent, content, beeInstance]);
|
|
412
608
|
|
|
413
|
-
|
|
609
|
+
const leftPanelExtractedTags = useMemo(() => {
|
|
610
|
+
if (channel === CHANNELS.SMS) {
|
|
611
|
+
const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
|
|
612
|
+
if (!smsTemplateHasMustacheTags(smsEditorBody)) return [];
|
|
613
|
+
const extractTagsFromApi = extractedTags ?? [];
|
|
614
|
+
if (extractTagsFromApi.length > 0) return extractTagsFromApi;
|
|
615
|
+
return buildSyntheticSmsMustacheTags(smsEditorBody);
|
|
616
|
+
}
|
|
617
|
+
const hasFallbackSmsBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
618
|
+
if (channel === CHANNELS.RCS && hasFallbackSmsBody) {
|
|
619
|
+
const rcsPrimaryTags = extractedTags ?? [];
|
|
620
|
+
const fallbackSmsTextForTags = smsFallbackTextForTagExtraction ?? '';
|
|
621
|
+
const fallbackSmsTagRows = smsTemplateHasMustacheTags(fallbackSmsTextForTags)
|
|
622
|
+
? (smsFallbackExtractedTags?.length > 0
|
|
623
|
+
? smsFallbackExtractedTags
|
|
624
|
+
: buildSyntheticSmsMustacheTags(fallbackSmsTextForTags))
|
|
625
|
+
: [];
|
|
626
|
+
const mergedRcsAndFallbackTags = [...rcsPrimaryTags, ...fallbackSmsTagRows];
|
|
627
|
+
if (mergedRcsAndFallbackTags.length > 0) return mergedRcsAndFallbackTags;
|
|
628
|
+
return buildSyntheticSmsMustacheTags(fallbackSmsTextForTags);
|
|
629
|
+
}
|
|
630
|
+
return extractedTags ?? [];
|
|
631
|
+
}, [
|
|
632
|
+
channel,
|
|
633
|
+
extractedTags,
|
|
634
|
+
getCurrentContent,
|
|
635
|
+
smsFallbackContent,
|
|
636
|
+
smsFallbackExtractedTags,
|
|
637
|
+
smsFallbackTextForTagExtraction,
|
|
638
|
+
]);
|
|
639
|
+
|
|
640
|
+
const isRcsSmsFallbackPreviewEnabled =
|
|
641
|
+
channel === CHANNELS.RCS
|
|
642
|
+
&& !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
643
|
+
// Only treat as SMS when user is on the Fallback SMS tab — not whenever fallback exists (RCS tab needs RCS preview API).
|
|
644
|
+
const isSmsFallbackTabActive = isRcsSmsFallbackPreviewEnabled && activePreviewTab === PREVIEW_TAB_SMS_FALLBACK;
|
|
645
|
+
const activeChannelForActions = isSmsFallbackTabActive ? CHANNELS.SMS : channel;
|
|
646
|
+
// VarSegment slot values live in rcsSmsFallbackVarMapped; raw templateContent alone is stale for /preview Body.
|
|
647
|
+
const resolvedSmsFallbackBodyForPreviewTab =
|
|
648
|
+
smsFallbackTextForTagExtraction
|
|
649
|
+
|| smsFallbackContent?.templateContent
|
|
650
|
+
|| smsFallbackContent?.content
|
|
651
|
+
|| '';
|
|
652
|
+
const activeContentForActions = isSmsFallbackTabActive
|
|
653
|
+
? resolvedSmsFallbackBodyForPreviewTab
|
|
654
|
+
: getCurrentContent;
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* SMS fallback pane must show /preview API result when user updated preview on that tab (plain text).
|
|
658
|
+
* Skip when resolvedBody is RCS-shaped (e.g. user last previewed on RCS tab).
|
|
659
|
+
*/
|
|
660
|
+
// smsFallbackPreviewText is the single source of truth for the resolved SMS fallback preview.
|
|
661
|
+
// It is set only by syncSmsFallbackPreview (called from handleUpdatePreview and the
|
|
662
|
+
// prefilled-values effect) and reset to undefined on discard / slidebox close.
|
|
663
|
+
// Using previewDataHtml as a fallback is unsafe because that state is shared with the primary
|
|
664
|
+
// RCS preview and can contain stale SMS or RCS content.
|
|
665
|
+
const smsFallbackResolvedText = useMemo(() => {
|
|
666
|
+
const hasFallbackBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
667
|
+
if (channel !== CHANNELS.RCS || !hasFallbackBody) return undefined;
|
|
668
|
+
if (smsFallbackPreviewText != null) return smsFallbackPreviewText;
|
|
669
|
+
return undefined;
|
|
670
|
+
}, [channel, smsFallbackContent, smsFallbackPreviewText]);
|
|
671
|
+
|
|
672
|
+
// Build test entities tree data from testCustomers prop
|
|
414
673
|
// Build test entities tree data from testCustomers prop (includes customers added via addTestCustomer action)
|
|
415
674
|
const testEntitiesTreeData = useMemo(() => {
|
|
416
675
|
const groupsNode = {
|
|
@@ -419,7 +678,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
419
678
|
selectable: false,
|
|
420
679
|
children: testGroups?.map((group) => ({
|
|
421
680
|
title: group?.groupName,
|
|
422
|
-
value:
|
|
681
|
+
value: normalizeTestEntityId(group?.groupId),
|
|
423
682
|
})),
|
|
424
683
|
};
|
|
425
684
|
|
|
@@ -429,7 +688,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
429
688
|
selectable: false,
|
|
430
689
|
children: testCustomers?.map((customer) => ({
|
|
431
690
|
title: customer?.name?.trim() || customer?.email?.trim() || customer?.mobile?.trim() || customer?.userId || customer?.customerId,
|
|
432
|
-
value:
|
|
691
|
+
value: normalizeTestEntityId(customer?.userId ?? customer?.customerId),
|
|
433
692
|
})) || [],
|
|
434
693
|
};
|
|
435
694
|
|
|
@@ -457,144 +716,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
457
716
|
return resolvedText;
|
|
458
717
|
};
|
|
459
718
|
|
|
460
|
-
const getViberMergedFormData = useCallback((formDataOverride) => {
|
|
461
|
-
const formDataObj = formDataOverride && typeof formDataOverride === 'object'
|
|
462
|
-
? formDataOverride
|
|
463
|
-
: (formData && typeof formData === 'object' ? formData : {});
|
|
464
|
-
let contentObj = {};
|
|
465
|
-
if (content && typeof content === 'object') {
|
|
466
|
-
contentObj = content;
|
|
467
|
-
} else if (typeof content === 'string' && content.trim()) {
|
|
468
|
-
try {
|
|
469
|
-
contentObj = JSON.parse(content);
|
|
470
|
-
} catch (e) {
|
|
471
|
-
contentObj = {};
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
const previewCards = formDataObj.viberPreviewContent?.cards
|
|
475
|
-
?? formDataObj.cards
|
|
476
|
-
?? contentObj.viberPreviewContent?.cards
|
|
477
|
-
?? contentObj.cards;
|
|
478
|
-
const mergedPreview = {
|
|
479
|
-
...(contentObj.viberPreviewContent || {}),
|
|
480
|
-
...(formDataObj.viberPreviewContent || {}),
|
|
481
|
-
...(Array.isArray(previewCards) && previewCards.length ? { cards: previewCards } : {}),
|
|
482
|
-
};
|
|
483
|
-
return {
|
|
484
|
-
...contentObj,
|
|
485
|
-
...formDataObj,
|
|
486
|
-
...(Array.isArray(previewCards) && previewCards.length ? { cards: previewCards } : {}),
|
|
487
|
-
...(Object.keys(mergedPreview).length ? { viberPreviewContent: mergedPreview } : {}),
|
|
488
|
-
};
|
|
489
|
-
}, [formData, content]);
|
|
490
|
-
|
|
491
|
-
const getViberCarouselCardsFromFormData = (formDataObj) => {
|
|
492
|
-
const previewCards = formDataObj?.viberPreviewContent?.cards;
|
|
493
|
-
if (Array.isArray(previewCards) && previewCards.length) {
|
|
494
|
-
return previewCards;
|
|
495
|
-
}
|
|
496
|
-
const rootCards = formDataObj?.cards;
|
|
497
|
-
if (Array.isArray(rootCards) && rootCards.length) {
|
|
498
|
-
return rootCards;
|
|
499
|
-
}
|
|
500
|
-
return [];
|
|
501
|
-
};
|
|
502
|
-
|
|
503
|
-
const getViberTagExtractionContent = (formDataObj, contentStr = '') => {
|
|
504
|
-
const messageText = formDataObj?.messageContent
|
|
505
|
-
|| formDataObj?.viberPreviewContent?.messageContent
|
|
506
|
-
|| contentStr
|
|
507
|
-
|| '';
|
|
508
|
-
const cardFieldTexts = getViberCarouselCardsFromFormData(formDataObj).flatMap((card) => {
|
|
509
|
-
const fields = [card?.text || ''];
|
|
510
|
-
(card?.buttons || []).forEach((button) => {
|
|
511
|
-
fields.push(button?.title || '', button?.action || '');
|
|
512
|
-
});
|
|
513
|
-
return fields;
|
|
514
|
-
});
|
|
515
|
-
return [messageText, ...cardFieldTexts]
|
|
516
|
-
.filter((value) => typeof value === 'string' && value.trim())
|
|
517
|
-
.join(' ');
|
|
518
|
-
};
|
|
519
|
-
|
|
520
|
-
const resolveViberCarouselCards = (cards, tagValues) => {
|
|
521
|
-
if (!Array.isArray(cards) || !tagValues || !Object.keys(tagValues).length) {
|
|
522
|
-
return cards;
|
|
523
|
-
}
|
|
524
|
-
return cards.map((card) => ({
|
|
525
|
-
...card,
|
|
526
|
-
text: resolveTagsInText(card?.text || '', tagValues),
|
|
527
|
-
buttons: (card?.buttons || []).map((button) => ({
|
|
528
|
-
...button,
|
|
529
|
-
title: resolveTagsInText(button?.title || '', tagValues),
|
|
530
|
-
action: resolveTagsInText(button?.action || '', tagValues),
|
|
531
|
-
})),
|
|
532
|
-
}));
|
|
533
|
-
};
|
|
534
|
-
|
|
535
|
-
const applyViberCustomValuesToContent = (contentObj, tagValues) => {
|
|
536
|
-
if (!contentObj || typeof contentObj !== 'object' || !tagValues || !Object.keys(tagValues).length) {
|
|
537
|
-
return contentObj;
|
|
538
|
-
}
|
|
539
|
-
const result = { ...contentObj };
|
|
540
|
-
if (typeof result.messageContent === 'string') {
|
|
541
|
-
result.messageContent = resolveTagsInText(result.messageContent, tagValues);
|
|
542
|
-
}
|
|
543
|
-
if (Array.isArray(result.cards)) {
|
|
544
|
-
result.cards = resolveViberCarouselCards(result.cards, tagValues);
|
|
545
|
-
}
|
|
546
|
-
if (result.viberPreviewContent && typeof result.viberPreviewContent === 'object') {
|
|
547
|
-
const preview = result.viberPreviewContent;
|
|
548
|
-
result.viberPreviewContent = {
|
|
549
|
-
...preview,
|
|
550
|
-
messageContent: resolveTagsInText(preview.messageContent || '', tagValues),
|
|
551
|
-
cards: resolveViberCarouselCards(preview.cards, tagValues),
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
return result;
|
|
555
|
-
};
|
|
556
|
-
|
|
557
|
-
const mergeViberPreviewWithResolved = (base, resolved) => {
|
|
558
|
-
if (!resolved || typeof resolved !== 'object') {
|
|
559
|
-
return base && typeof base === 'object' ? base : {};
|
|
560
|
-
}
|
|
561
|
-
const baseObj = base && typeof base === 'object' ? base : {};
|
|
562
|
-
const basePreview = baseObj.viberPreviewContent || {};
|
|
563
|
-
const resolvedPreview = resolved.viberPreviewContent || {};
|
|
564
|
-
const baseCards = basePreview.cards || baseObj.cards || [];
|
|
565
|
-
const resolvedCards = resolvedPreview.cards || resolved.cards || [];
|
|
566
|
-
const mergedCards = baseCards.length
|
|
567
|
-
? baseCards.map((card, index) => {
|
|
568
|
-
const resolvedCard = resolvedCards[index];
|
|
569
|
-
if (!resolvedCard) {
|
|
570
|
-
return card;
|
|
571
|
-
}
|
|
572
|
-
return {
|
|
573
|
-
...card,
|
|
574
|
-
...(resolvedCard.text != null ? { text: resolvedCard.text } : {}),
|
|
575
|
-
...(resolvedCard.mediaUrl != null ? { mediaUrl: resolvedCard.mediaUrl } : {}),
|
|
576
|
-
buttons: (card.buttons || []).map((button, buttonIndex) => ({
|
|
577
|
-
...button,
|
|
578
|
-
...(resolvedCard.buttons?.[buttonIndex] || {}),
|
|
579
|
-
})),
|
|
580
|
-
};
|
|
581
|
-
})
|
|
582
|
-
: resolvedCards;
|
|
583
|
-
const resolvedMessage = resolved.messageContent ?? resolvedPreview.messageContent;
|
|
584
|
-
|
|
585
|
-
return {
|
|
586
|
-
...baseObj,
|
|
587
|
-
...resolved,
|
|
588
|
-
viberPreviewContent: {
|
|
589
|
-
...basePreview,
|
|
590
|
-
...resolvedPreview,
|
|
591
|
-
type: resolvedPreview.type || basePreview.type || baseObj.type,
|
|
592
|
-
cards: mergedCards.length ? mergedCards : (resolvedPreview.cards || basePreview.cards),
|
|
593
|
-
...(resolvedMessage != null ? { messageContent: resolvedMessage } : {}),
|
|
594
|
-
},
|
|
595
|
-
};
|
|
596
|
-
};
|
|
597
|
-
|
|
598
719
|
/**
|
|
599
720
|
* Common handler for saving test customers (both new and existing)
|
|
600
721
|
*/
|
|
@@ -647,11 +768,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
647
768
|
email: customerData?.email || '',
|
|
648
769
|
mobile: customerData?.mobile || '',
|
|
649
770
|
});
|
|
650
|
-
const prefixedAddedId = 'customer:' + normalizedAddedId;
|
|
651
771
|
setSelectedTestEntities((prev) => (
|
|
652
|
-
prev.some((id) => id
|
|
772
|
+
prev.some((id) => testEntityIdsEqual(id, normalizedAddedId))
|
|
653
773
|
? prev
|
|
654
|
-
: [...prev,
|
|
774
|
+
: [...prev, normalizedAddedId]
|
|
655
775
|
));
|
|
656
776
|
}
|
|
657
777
|
handleCloseCustomerModal();
|
|
@@ -715,13 +835,33 @@ const CommonTestAndPreview = (props) => {
|
|
|
715
835
|
whatsappContent: formDataObj?.whatsappContent,
|
|
716
836
|
};
|
|
717
837
|
|
|
718
|
-
case CHANNELS.RCS:
|
|
838
|
+
case CHANNELS.RCS: {
|
|
839
|
+
// For carousel, replace {{N}} tokens in carouselData with resolved semantic vars from
|
|
840
|
+
// formData.cardContent so the preview API can resolve {{member.firstName}} etc.
|
|
841
|
+
const formDataCardContent =
|
|
842
|
+
formDataObj?.versions?.base?.content?.RCS?.rcsContent?.cardContent
|
|
843
|
+
?? formDataObj?.content?.RCS?.rcsContent?.cardContent;
|
|
844
|
+
let parsedRcs = {};
|
|
845
|
+
try { parsedRcs = JSON.parse(contentStr); } catch (e) { parsedRcs = typeof contentStr === 'object' ? contentStr : {}; }
|
|
846
|
+
if (Array.isArray(parsedRcs.carouselData) && Array.isArray(formDataCardContent) && formDataCardContent.length > 0) {
|
|
847
|
+
const resolvedCarouselData = parsedRcs.carouselData.map((card, idx) => {
|
|
848
|
+
const formCard = formDataCardContent[idx] || {};
|
|
849
|
+
return {
|
|
850
|
+
...card,
|
|
851
|
+
title: formCard.title || card.title,
|
|
852
|
+
bodyText: formCard.description || card.bodyText,
|
|
853
|
+
};
|
|
854
|
+
});
|
|
855
|
+
return {
|
|
856
|
+
...basePayload,
|
|
857
|
+
messageBody: JSON.stringify({ ...parsedRcs, carouselData: resolvedCarouselData }),
|
|
858
|
+
};
|
|
859
|
+
}
|
|
719
860
|
return {
|
|
720
861
|
...basePayload,
|
|
721
862
|
messageBody: contentStr,
|
|
722
|
-
// messageTitle: resolveTagsInText(formDataObj?.rcsTitle || '', customValuesObj),
|
|
723
|
-
// rcsDesc: formDataObj?.rcsDesc,
|
|
724
863
|
};
|
|
864
|
+
}
|
|
725
865
|
|
|
726
866
|
case CHANNELS.INAPP:
|
|
727
867
|
return {
|
|
@@ -750,11 +890,48 @@ const CommonTestAndPreview = (props) => {
|
|
|
750
890
|
messageBody: contentStr,
|
|
751
891
|
};
|
|
752
892
|
|
|
893
|
+
case CHANNELS.WEBPUSH:
|
|
894
|
+
return {
|
|
895
|
+
...basePayload,
|
|
896
|
+
messageBody: contentStr,
|
|
897
|
+
};
|
|
898
|
+
|
|
753
899
|
default:
|
|
754
900
|
return basePayload;
|
|
755
901
|
}
|
|
756
902
|
};
|
|
757
903
|
|
|
904
|
+
/**
|
|
905
|
+
* When RCS has SMS fallback, refresh fallback preview text via the same Liquid /preview API
|
|
906
|
+
* (separate call with SMS channel + fallback template body). Used after primary preview updates.
|
|
907
|
+
*/
|
|
908
|
+
const syncSmsFallbackPreview = async (customValuesForResolve, selectedCustomerObj) => {
|
|
909
|
+
const fallbackBodyForLiquidPreview =
|
|
910
|
+
getSmsFallbackTextForTagExtraction(smsFallbackContent)
|
|
911
|
+
|| smsFallbackContent?.templateContent
|
|
912
|
+
|| smsFallbackContent?.content
|
|
913
|
+
|| '';
|
|
914
|
+
if (channel !== CHANNELS.RCS || !String(fallbackBodyForLiquidPreview).trim()) return;
|
|
915
|
+
try {
|
|
916
|
+
const smsFallbackPayload = preparePreviewPayload(
|
|
917
|
+
CHANNELS.SMS,
|
|
918
|
+
formData || {},
|
|
919
|
+
fallbackBodyForLiquidPreview,
|
|
920
|
+
customValuesForResolve,
|
|
921
|
+
selectedCustomerObj
|
|
922
|
+
);
|
|
923
|
+
const fallbackResponse = await Api.updateEmailPreview(smsFallbackPayload);
|
|
924
|
+
const fallbackPreview = extractPreviewFromLiquidResponse(fallbackResponse);
|
|
925
|
+
setSmsFallbackPreviewText(
|
|
926
|
+
typeof fallbackPreview?.resolvedBody === 'string'
|
|
927
|
+
? fallbackPreview.resolvedBody
|
|
928
|
+
: undefined
|
|
929
|
+
);
|
|
930
|
+
} catch (e) {
|
|
931
|
+
/* keep existing smsFallbackPreviewText on failure */
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
|
|
758
935
|
/**
|
|
759
936
|
* Prepare payload for tag extraction based on channel
|
|
760
937
|
*/
|
|
@@ -778,11 +955,32 @@ const CommonTestAndPreview = (props) => {
|
|
|
778
955
|
templateContent: contentStr,
|
|
779
956
|
};
|
|
780
957
|
|
|
781
|
-
case CHANNELS.RCS:
|
|
958
|
+
case CHANNELS.RCS: {
|
|
959
|
+
// Carousel templates don't have rcsTitle/rcsDesc — their content lives in cardContent[].
|
|
960
|
+
// Use the resolved card strings from formDataObj (resolved by resolveTemplateWithMap in
|
|
961
|
+
// testPreviewFormData) so the extraction API receives actual Capillary tag expressions
|
|
962
|
+
// instead of numeric {{1}}/{{2}} placeholders.
|
|
963
|
+
const rcsCardContentArr =
|
|
964
|
+
formDataObj?.versions?.base?.content?.RCS?.rcsContent?.cardContent
|
|
965
|
+
?? formDataObj?.content?.RCS?.rcsContent?.cardContent;
|
|
966
|
+
if (Array.isArray(rcsCardContentArr) && rcsCardContentArr.length > 0) {
|
|
967
|
+
const carouselTagText = rcsCardContentArr
|
|
968
|
+
.map((c) => `${c.title || ''} ${c.description || ''}`)
|
|
969
|
+
.join(' ')
|
|
970
|
+
.trim();
|
|
971
|
+
if (carouselTagText) {
|
|
972
|
+
return {
|
|
973
|
+
templateSubject: formDataObj?.rcsTitle || '',
|
|
974
|
+
templateContent: carouselTagText,
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
// Standalone (non-carousel) card: use rcsTitle/rcsDesc directly.
|
|
782
979
|
return {
|
|
783
980
|
templateSubject: formDataObj?.rcsTitle || '',
|
|
784
981
|
templateContent: formDataObj?.rcsDesc || contentStr,
|
|
785
982
|
};
|
|
983
|
+
}
|
|
786
984
|
|
|
787
985
|
case CHANNELS.INAPP:
|
|
788
986
|
return {
|
|
@@ -796,13 +994,17 @@ const CommonTestAndPreview = (props) => {
|
|
|
796
994
|
templateContent: formDataObj?.bodyText || contentStr,
|
|
797
995
|
};
|
|
798
996
|
|
|
799
|
-
case CHANNELS.VIBER:
|
|
800
|
-
const viberFormData = getViberMergedFormData(formDataObj);
|
|
997
|
+
case CHANNELS.VIBER:
|
|
801
998
|
return {
|
|
802
|
-
templateSubject:
|
|
803
|
-
templateContent:
|
|
999
|
+
templateSubject: formDataObj?.messageTitle || '',
|
|
1000
|
+
templateContent: contentStr,
|
|
1001
|
+
};
|
|
1002
|
+
|
|
1003
|
+
case CHANNELS.WEBPUSH:
|
|
1004
|
+
return {
|
|
1005
|
+
templateSubject: formDataObj?.content?.title || '',
|
|
1006
|
+
templateContent: contentStr,
|
|
804
1007
|
};
|
|
805
|
-
}
|
|
806
1008
|
|
|
807
1009
|
case CHANNELS.ZALO: {
|
|
808
1010
|
// For Zalo, extract content from templateListParams array
|
|
@@ -851,7 +1053,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
851
1053
|
} = carousel || {};
|
|
852
1054
|
const buttonData = buttons.map((button, index) => {
|
|
853
1055
|
const {
|
|
854
|
-
type, text, phone_number, urlType, url,
|
|
1056
|
+
type, text, phone_number: phoneNumber, urlType, url,
|
|
855
1057
|
} = button || {};
|
|
856
1058
|
const buttonObj = {
|
|
857
1059
|
type,
|
|
@@ -859,7 +1061,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
859
1061
|
index,
|
|
860
1062
|
};
|
|
861
1063
|
if (type === PHONE_NUMBER) {
|
|
862
|
-
buttonObj.phoneNumber =
|
|
1064
|
+
buttonObj.phoneNumber = phoneNumber;
|
|
863
1065
|
}
|
|
864
1066
|
if (type === URL) {
|
|
865
1067
|
buttonObj.url = url;
|
|
@@ -888,7 +1090,159 @@ const CommonTestAndPreview = (props) => {
|
|
|
888
1090
|
};
|
|
889
1091
|
});
|
|
890
1092
|
|
|
891
|
-
|
|
1093
|
+
/**
|
|
1094
|
+
* Build createMessageMeta payload for RCS (test message).
|
|
1095
|
+
* rcsMessageContent: { channel, accountId?, rcsRichCardContent: { contentType, cardType, cardSettings, cardContent }, smsFallBackContent? }
|
|
1096
|
+
* Then rcsDeliverySettings, executionParams, clientName last.
|
|
1097
|
+
*/
|
|
1098
|
+
const buildRcsTestMessagePayload = (
|
|
1099
|
+
creativeFormData,
|
|
1100
|
+
_unusedEditorContentString,
|
|
1101
|
+
_customValuesObj,
|
|
1102
|
+
deliverySettingsOverride,
|
|
1103
|
+
basePayload,
|
|
1104
|
+
_rcsTestMetaExtras = {},
|
|
1105
|
+
) => {
|
|
1106
|
+
const rcsSectionFromForm =
|
|
1107
|
+
creativeFormData?.versions?.base?.content?.RCS ?? creativeFormData?.content?.RCS ?? {};
|
|
1108
|
+
const rcsContentFromForm = rcsSectionFromForm?.rcsContent || {};
|
|
1109
|
+
const smsFallbackFromCreativeForm = rcsSectionFromForm?.smsFallBackContent || {};
|
|
1110
|
+
let rcsCardPayloadList = [];
|
|
1111
|
+
if (Array.isArray(rcsContentFromForm?.cardContent)) {
|
|
1112
|
+
rcsCardPayloadList = rcsContentFromForm.cardContent;
|
|
1113
|
+
} else if (rcsContentFromForm?.cardContent) {
|
|
1114
|
+
rcsCardPayloadList = [rcsContentFromForm.cardContent];
|
|
1115
|
+
}
|
|
1116
|
+
// Raw title/description with template tags; SMS fallback uses tagged template fields (pickFirst…).
|
|
1117
|
+
const cardContentForTestMetaApi = rcsCardPayloadList.map((singleRcsCardPayload) => {
|
|
1118
|
+
const normalizedCardMediaForTestApi = singleRcsCardPayload?.media
|
|
1119
|
+
? normalizeRcsTestCardMedia(singleRcsCardPayload.media)
|
|
1120
|
+
: undefined;
|
|
1121
|
+
const suggestionsFromCard = Array.isArray(singleRcsCardPayload?.suggestions)
|
|
1122
|
+
? singleRcsCardPayload.suggestions
|
|
1123
|
+
: [];
|
|
1124
|
+
const suggestionsFormattedForTestMeta = suggestionsFromCard.map((suggestionItem, index) =>
|
|
1125
|
+
mapRcsSuggestionForTestMeta(suggestionItem, index));
|
|
1126
|
+
return {
|
|
1127
|
+
title: singleRcsCardPayload?.title ?? '',
|
|
1128
|
+
description: singleRcsCardPayload?.description ?? '',
|
|
1129
|
+
mediaType: singleRcsCardPayload?.mediaType ?? MEDIA_TYPE_TEXT,
|
|
1130
|
+
...(normalizedCardMediaForTestApi && { media: normalizedCardMediaForTestApi }),
|
|
1131
|
+
...(suggestionsFormattedForTestMeta.length > 0 && {
|
|
1132
|
+
suggestions: suggestionsFormattedForTestMeta,
|
|
1133
|
+
}),
|
|
1134
|
+
};
|
|
1135
|
+
});
|
|
1136
|
+
// Use the component-level smsFallbackContent prop (has rcsSmsFallbackVarMapped) so DLT
|
|
1137
|
+
// {#var#} slots are converted to {{tagName}} mustache tags before sending to createMessageMeta.
|
|
1138
|
+
const smsFallbackTaggedTemplateBody =
|
|
1139
|
+
getSmsFallbackTextForTagExtraction(smsFallbackContent)
|
|
1140
|
+
|| pickFirstSmsFallbackTemplateString(smsFallbackFromCreativeForm)
|
|
1141
|
+
|| '';
|
|
1142
|
+
const smsSenderFromDelivery = deliverySettingsOverride?.cdmaSenderId?.includes('|')
|
|
1143
|
+
? deliverySettingsOverride.cdmaSenderId.split('|')[1]
|
|
1144
|
+
: deliverySettingsOverride?.cdmaSenderId;
|
|
1145
|
+
const deliveryFallbackSmsId =
|
|
1146
|
+
typeof smsSenderFromDelivery === 'string' ? smsSenderFromDelivery.trim() : '';
|
|
1147
|
+
const creativeFallbackSmsId =
|
|
1148
|
+
smsFallbackFromCreativeForm?.senderId != null
|
|
1149
|
+
? String(smsFallbackFromCreativeForm.senderId).trim()
|
|
1150
|
+
: '';
|
|
1151
|
+
const fallbackSmsSenderIdForChannel = deliveryFallbackSmsId || creativeFallbackSmsId || '';
|
|
1152
|
+
|
|
1153
|
+
// For DLT orgs: include templateConfigs inside smsFallBackContent so createMessageMeta
|
|
1154
|
+
// receives the DLT template ID, registered sender IDs, and body alongside the message.
|
|
1155
|
+
// Priority: smsFallBackContent.templateConfigs from the RCS payload (clean API shape,
|
|
1156
|
+
// built by createPayload) → fallback to root-level formData.templateConfigs (strip the
|
|
1157
|
+
// UI-only traiDltEnabled flag before sending to the API).
|
|
1158
|
+
const smsFallbackTemplateConfigsForApi = (() => {
|
|
1159
|
+
if (
|
|
1160
|
+
smsFallbackFromCreativeForm?.templateConfigs
|
|
1161
|
+
&& typeof smsFallbackFromCreativeForm.templateConfigs === 'object'
|
|
1162
|
+
) {
|
|
1163
|
+
return smsFallbackFromCreativeForm.templateConfigs;
|
|
1164
|
+
}
|
|
1165
|
+
const rootTc = creativeFormData?.templateConfigs;
|
|
1166
|
+
if (rootTc && typeof rootTc === 'object') {
|
|
1167
|
+
// eslint-disable-next-line no-unused-vars
|
|
1168
|
+
const { traiDltEnabled: _uiFlag, ...tcForApi } = rootTc;
|
|
1169
|
+
return Object.keys(tcForApi).length > 0 ? tcForApi : null;
|
|
1170
|
+
}
|
|
1171
|
+
return null;
|
|
1172
|
+
})();
|
|
1173
|
+
|
|
1174
|
+
const smsFallBackContent =
|
|
1175
|
+
smsFallbackTaggedTemplateBody.trim() !== ''
|
|
1176
|
+
? {
|
|
1177
|
+
message: smsFallbackTaggedTemplateBody,
|
|
1178
|
+
...(smsFallbackTemplateConfigsForApi && {
|
|
1179
|
+
templateConfigs: smsFallbackTemplateConfigsForApi,
|
|
1180
|
+
}),
|
|
1181
|
+
}
|
|
1182
|
+
: undefined;
|
|
1183
|
+
|
|
1184
|
+
// accountId: WeCRM account id (not sourceAccountIdentifier) for createMessageMeta
|
|
1185
|
+
const accountIdForMeta =
|
|
1186
|
+
rcsContentFromForm?.accountId != null && String(rcsContentFromForm.accountId).trim() !== ''
|
|
1187
|
+
? String(rcsContentFromForm.accountId)
|
|
1188
|
+
: undefined;
|
|
1189
|
+
|
|
1190
|
+
const rcsRichCardContent = {
|
|
1191
|
+
contentType: RCS_TEST_META_CONTENT_TYPE_RICHCARD,
|
|
1192
|
+
cardType: rcsContentFromForm?.cardType ?? RCS_TEST_META_CARD_TYPE_STANDALONE,
|
|
1193
|
+
cardSettings: rcsContentFromForm?.cardSettings ?? {
|
|
1194
|
+
cardOrientation: RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
|
|
1195
|
+
cardWidth: RCS_TEST_META_CARD_WIDTH_SMALL,
|
|
1196
|
+
},
|
|
1197
|
+
...(cardContentForTestMetaApi.length > 0 && { cardContent: cardContentForTestMetaApi }),
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
const rcsMessageContent = {
|
|
1201
|
+
channel: CHANNELS.RCS,
|
|
1202
|
+
...(accountIdForMeta && { accountId: accountIdForMeta }),
|
|
1203
|
+
rcsRichCardContent,
|
|
1204
|
+
...(smsFallBackContent && { smsFallBackContent }),
|
|
1205
|
+
};
|
|
1206
|
+
const rcsComposite = deliverySettingsOverride?.gsmSenderId ?? '';
|
|
1207
|
+
const [rcsDomainId, rcsSenderId] = rcsComposite.includes('|') ? rcsComposite.split('|') : ['', rcsComposite];
|
|
1208
|
+
const rcsDeliverySettings = {
|
|
1209
|
+
channelSettings: {
|
|
1210
|
+
channel: CHANNELS.RCS,
|
|
1211
|
+
rcsSender: (rcsSenderId || deliverySettingsOverride?.rcsSender) ?? '',
|
|
1212
|
+
domainId:
|
|
1213
|
+
rcsDomainId !== '' && rcsDomainId !== undefined && !Number.isNaN(Number(rcsDomainId))
|
|
1214
|
+
? Number(rcsDomainId)
|
|
1215
|
+
: (deliverySettingsOverride?.domainId ?? 0),
|
|
1216
|
+
fallbackSmsSenderId: fallbackSmsSenderIdForChannel,
|
|
1217
|
+
},
|
|
1218
|
+
additionalSettings: {
|
|
1219
|
+
useTinyUrl: false,
|
|
1220
|
+
encryptUrl: false,
|
|
1221
|
+
linkTrackingEnabled: false,
|
|
1222
|
+
bypassControlUser: false,
|
|
1223
|
+
userSubscriptionDisabled: false,
|
|
1224
|
+
},
|
|
1225
|
+
};
|
|
1226
|
+
const { clientName: baseClientName = CLIENT_NAME_CREATIVES, ...restBase } = basePayload;
|
|
1227
|
+
return {
|
|
1228
|
+
...restBase,
|
|
1229
|
+
rcsMessageContent,
|
|
1230
|
+
rcsDeliverySettings,
|
|
1231
|
+
executionParams: {},
|
|
1232
|
+
clientName: baseClientName,
|
|
1233
|
+
};
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
const prepareTestMessagePayload = (
|
|
1237
|
+
channelType,
|
|
1238
|
+
formDataObj,
|
|
1239
|
+
contentStr,
|
|
1240
|
+
customValuesObj,
|
|
1241
|
+
recipientDetails,
|
|
1242
|
+
previewDataObj,
|
|
1243
|
+
deliverySettingsOverride,
|
|
1244
|
+
rcsExtra = {},
|
|
1245
|
+
) => {
|
|
892
1246
|
// Base payload structure common to all channels
|
|
893
1247
|
const basePayload = {
|
|
894
1248
|
ouId: -1,
|
|
@@ -969,12 +1323,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
969
1323
|
},
|
|
970
1324
|
smsDeliverySettings: {
|
|
971
1325
|
channelSettings: {
|
|
1326
|
+
channel: CHANNELS.SMS,
|
|
972
1327
|
gsmSenderId: deliverySettingsOverride?.gsmSenderId ?? '',
|
|
973
1328
|
domainId: deliverySettingsOverride?.domainId ?? null,
|
|
974
1329
|
domainGatewayMapId: deliverySettingsOverride?.domainGatewayMapId ?? '',
|
|
975
1330
|
targetNdnc: false,
|
|
976
1331
|
cdmaSenderId: deliverySettingsOverride?.cdmaSenderId ?? '',
|
|
977
|
-
channel: CHANNELS.SMS,
|
|
978
1332
|
},
|
|
979
1333
|
additionalSettings: {
|
|
980
1334
|
useTinyUrl: false,
|
|
@@ -1118,7 +1472,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
1118
1472
|
return {
|
|
1119
1473
|
...basePayload,
|
|
1120
1474
|
whatsappMessageContent: {
|
|
1121
|
-
messageBody: templateEditorValue || '',
|
|
1475
|
+
messageBody: resolvedMessageBody || templateEditorValue || '',
|
|
1122
1476
|
accountId: formDataObj?.accountId || '',
|
|
1123
1477
|
sourceAccountIdentifier: sourceAccountIdentifier || formDataObj?.sourceAccountIdentifier || '',
|
|
1124
1478
|
accountName: formDataObj?.accountName || '',
|
|
@@ -1143,16 +1497,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
1143
1497
|
}
|
|
1144
1498
|
|
|
1145
1499
|
case CHANNELS.RCS:
|
|
1146
|
-
return
|
|
1147
|
-
...basePayload,
|
|
1148
|
-
rcsMessageContent: {
|
|
1149
|
-
channel: CHANNELS.RCS,
|
|
1150
|
-
messageBody: contentStr,
|
|
1151
|
-
rcsType: additionalProps?.rcsType,
|
|
1152
|
-
rcsImageSrc: formDataObj?.rcsImageSrc,
|
|
1153
|
-
rcsSuggestions: formDataObj?.rcsSuggestions,
|
|
1154
|
-
},
|
|
1155
|
-
};
|
|
1500
|
+
return buildRcsTestMessagePayload(formDataObj, contentStr, customValuesObj, deliverySettingsOverride, basePayload, rcsExtra);
|
|
1156
1501
|
|
|
1157
1502
|
case CHANNELS.INAPP: {
|
|
1158
1503
|
// InApp payload structure similar to MobilePush
|
|
@@ -1691,16 +2036,11 @@ const CommonTestAndPreview = (props) => {
|
|
|
1691
2036
|
// 1. formDataObj from getTemplateContent (contains both viberPreviewContent and payload fields)
|
|
1692
2037
|
// 2. formDataObj[0] (array format - legacy)
|
|
1693
2038
|
// 3. contentStr (direct string - legacy)
|
|
1694
|
-
// Merge content prop so listing / preview-only flows include carousel card tags
|
|
1695
|
-
const viberFormData = getViberMergedFormData(formDataObj);
|
|
1696
|
-
formDataObj = viberFormData;
|
|
1697
2039
|
|
|
1698
2040
|
let messageText = '';
|
|
1699
2041
|
let imageData = null;
|
|
1700
2042
|
let videoData = null;
|
|
1701
2043
|
let buttonData = null;
|
|
1702
|
-
let cardsData = [];
|
|
1703
|
-
let messageType = '';
|
|
1704
2044
|
let accountId = null;
|
|
1705
2045
|
let accountDetails = null;
|
|
1706
2046
|
let scenarioKey = '';
|
|
@@ -1715,8 +2055,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
1715
2055
|
imageData = formDataObj.image || null;
|
|
1716
2056
|
videoData = formDataObj.video || null;
|
|
1717
2057
|
buttonData = formDataObj.button || null;
|
|
1718
|
-
cardsData = formDataObj.cards || [];
|
|
1719
|
-
messageType = formDataObj.type || '';
|
|
1720
2058
|
accountId = formDataObj.accountId || null;
|
|
1721
2059
|
accountDetails = formDataObj.accountDetails || null;
|
|
1722
2060
|
scenarioKey = formDataObj.scenarioKey || VIBER_API_SCENARIO_KEY;
|
|
@@ -1743,8 +2081,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
1743
2081
|
url: formDataObj?.buttonURL || '',
|
|
1744
2082
|
};
|
|
1745
2083
|
}
|
|
1746
|
-
cardsData = viberPreview.cards || formDataObj?.cards || [];
|
|
1747
|
-
messageType = viberPreview.type || formDataObj?.type || '';
|
|
1748
2084
|
// Extract account info from parent formDataObj if available
|
|
1749
2085
|
accountId = formDataObj.accountId || null;
|
|
1750
2086
|
accountDetails = formDataObj.accountDetails || null;
|
|
@@ -1787,10 +2123,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
1787
2123
|
text: messageText,
|
|
1788
2124
|
};
|
|
1789
2125
|
|
|
1790
|
-
if (messageType === CHANNELS.VIBER) {
|
|
1791
|
-
messageType = '';
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
2126
|
// Add image if present
|
|
1795
2127
|
if (imageData && imageData.url) {
|
|
1796
2128
|
viberContent.image = {
|
|
@@ -1819,34 +2151,9 @@ const CommonTestAndPreview = (props) => {
|
|
|
1819
2151
|
}
|
|
1820
2152
|
}
|
|
1821
2153
|
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
viberContent.
|
|
1825
|
-
text: card?.text || '',
|
|
1826
|
-
mediaUrl: card?.mediaUrl || '',
|
|
1827
|
-
buttons: (card?.buttons || []).map((button) => ({
|
|
1828
|
-
title: button?.title || '',
|
|
1829
|
-
action: button?.action || '',
|
|
1830
|
-
})),
|
|
1831
|
-
}));
|
|
1832
|
-
delete viberContent.button;
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1835
|
-
// Resolve tags in text/cards if custom values are provided
|
|
1836
|
-
if (customValuesObj && Object.keys(customValuesObj).length > 0) {
|
|
1837
|
-
if (viberContent.text) {
|
|
1838
|
-
viberContent.text = resolveTagsInText(viberContent.text, customValuesObj);
|
|
1839
|
-
}
|
|
1840
|
-
if (Array.isArray(viberContent.cards)) {
|
|
1841
|
-
viberContent.cards = resolveViberCarouselCards(viberContent.cards, customValuesObj);
|
|
1842
|
-
}
|
|
1843
|
-
if (viberContent.button) {
|
|
1844
|
-
viberContent.button = {
|
|
1845
|
-
...viberContent.button,
|
|
1846
|
-
text: resolveTagsInText(viberContent.button.text || '', customValuesObj),
|
|
1847
|
-
url: resolveTagsInText(viberContent.button.url || '', customValuesObj),
|
|
1848
|
-
};
|
|
1849
|
-
}
|
|
2154
|
+
// Resolve tags in text if custom values are provided
|
|
2155
|
+
if (customValuesObj && Object.keys(customValuesObj).length > 0 && viberContent.text) {
|
|
2156
|
+
viberContent.text = resolveTagsInText(viberContent.text, customValuesObj);
|
|
1850
2157
|
}
|
|
1851
2158
|
|
|
1852
2159
|
// Build messageBody JSON string
|
|
@@ -1981,6 +2288,42 @@ const CommonTestAndPreview = (props) => {
|
|
|
1981
2288
|
};
|
|
1982
2289
|
}
|
|
1983
2290
|
|
|
2291
|
+
case CHANNELS.WEBPUSH: {
|
|
2292
|
+
const webpushData = (typeof formDataObj === 'object' && formDataObj !== null)
|
|
2293
|
+
? formDataObj
|
|
2294
|
+
: {};
|
|
2295
|
+
const innerContent = webpushData?.content || {};
|
|
2296
|
+
|
|
2297
|
+
const resolvedTitle = resolveTagsInText(innerContent?.title || '', customValuesObj);
|
|
2298
|
+
const resolvedMessage = resolveTagsInText(innerContent?.message || '', customValuesObj);
|
|
2299
|
+
|
|
2300
|
+
return {
|
|
2301
|
+
...basePayload,
|
|
2302
|
+
webPushMessageContent: {
|
|
2303
|
+
channel: CHANNELS.WEBPUSH,
|
|
2304
|
+
accountId: webpushData?.accountId || null,
|
|
2305
|
+
content: {
|
|
2306
|
+
title: resolvedTitle,
|
|
2307
|
+
message: resolvedMessage,
|
|
2308
|
+
...(innerContent?.iconImageUrl && { iconImageUrl: innerContent.iconImageUrl }),
|
|
2309
|
+
...(innerContent?.cta && { cta: innerContent.cta }),
|
|
2310
|
+
...(innerContent?.expandableDetails && { expandableDetails: innerContent.expandableDetails }),
|
|
2311
|
+
},
|
|
2312
|
+
messageSubject: webpushData?.messageSubject || resolvedTitle || '',
|
|
2313
|
+
},
|
|
2314
|
+
webPushDeliverySettings: {
|
|
2315
|
+
channelSettings: {
|
|
2316
|
+
channel: CHANNELS.WEBPUSH,
|
|
2317
|
+
notificationTtl: {
|
|
2318
|
+
duration: 7,
|
|
2319
|
+
timeUnit: DAYS,
|
|
2320
|
+
},
|
|
2321
|
+
},
|
|
2322
|
+
additionalSettings: {},
|
|
2323
|
+
},
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
|
|
1984
2327
|
default:
|
|
1985
2328
|
return basePayload;
|
|
1986
2329
|
}
|
|
@@ -1993,6 +2336,46 @@ const CommonTestAndPreview = (props) => {
|
|
|
1993
2336
|
*
|
|
1994
2337
|
* IMPORTANT: Use raw content/formData initially, only use previewDataHtml if preview call was made
|
|
1995
2338
|
*/
|
|
2339
|
+
// Memoized RCS carousel content: only recomputes when the API response or raw template changes,
|
|
2340
|
+
// NOT on every customValues keystroke. Uses previewCustomValuesRef captured at Update Preview time.
|
|
2341
|
+
const rcsCarouselContentObj = useMemo(() => {
|
|
2342
|
+
if (channel !== CHANNELS.RCS) return null;
|
|
2343
|
+
let resolvedContent = null;
|
|
2344
|
+
if (hasPreviewCallBeenMade && previewDataHtml?.resolvedBody) {
|
|
2345
|
+
resolvedContent = previewDataHtml.resolvedBody;
|
|
2346
|
+
if (typeof resolvedContent === 'string') {
|
|
2347
|
+
try { resolvedContent = JSON.parse(resolvedContent); } catch (e) { resolvedContent = null; }
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
if (resolvedContent && typeof resolvedContent === 'object') return resolvedContent;
|
|
2351
|
+
|
|
2352
|
+
let rawRcs = content;
|
|
2353
|
+
if (typeof rawRcs === 'string') {
|
|
2354
|
+
try { rawRcs = JSON.parse(rawRcs); } catch (e) { rawRcs = content; }
|
|
2355
|
+
}
|
|
2356
|
+
const snapCustomValues = previewCustomValuesRef.current;
|
|
2357
|
+
const hasEnteredValues = snapCustomValues && Object.keys(snapCustomValues).some((k) => snapCustomValues[k]);
|
|
2358
|
+
if (hasPreviewCallBeenMade && hasEnteredValues && Array.isArray(rawRcs?.carouselData)) {
|
|
2359
|
+
const formDataCardContent =
|
|
2360
|
+
formData?.versions?.base?.content?.RCS?.rcsContent?.cardContent
|
|
2361
|
+
?? formData?.content?.RCS?.rcsContent?.cardContent
|
|
2362
|
+
?? [];
|
|
2363
|
+
return {
|
|
2364
|
+
...rawRcs,
|
|
2365
|
+
carouselData: rawRcs.carouselData.map((card, idx) => {
|
|
2366
|
+
const formCard = formDataCardContent[idx] || {};
|
|
2367
|
+
return {
|
|
2368
|
+
...card,
|
|
2369
|
+
title: resolveTagsInText(formCard.title || card.title || '', snapCustomValues),
|
|
2370
|
+
bodyText: resolveTagsInText(formCard.description || card.bodyText || '', snapCustomValues),
|
|
2371
|
+
};
|
|
2372
|
+
}),
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
return rawRcs || {};
|
|
2376
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2377
|
+
}, [channel, content, hasPreviewCallBeenMade, previewDataHtml, formData]);
|
|
2378
|
+
|
|
1996
2379
|
const prepareUnifiedPreviewProps = () => {
|
|
1997
2380
|
// Prepare content based on channel
|
|
1998
2381
|
let contentObj = {};
|
|
@@ -2012,7 +2395,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2012
2395
|
contentObj = hasPreviewCallBeenMade && previewDataHtml?.resolvedBody
|
|
2013
2396
|
? previewDataHtml.resolvedBody
|
|
2014
2397
|
: getCurrentContent || '';
|
|
2015
|
-
} else if (
|
|
2398
|
+
} else if ([CHANNELS.WHATSAPP, CHANNELS.WEBPUSH].includes(channel)) {
|
|
2016
2399
|
// For WhatsApp, content is an object with templateMsg, media, CTA, etc.
|
|
2017
2400
|
// Content comes from WhatsApp component state, passed via content prop
|
|
2018
2401
|
let resolvedContent = null;
|
|
@@ -2054,42 +2437,14 @@ const CommonTestAndPreview = (props) => {
|
|
|
2054
2437
|
contentObj = parsedContent || {};
|
|
2055
2438
|
}
|
|
2056
2439
|
} else if (channel === CHANNELS.RCS) {
|
|
2057
|
-
//
|
|
2058
|
-
//
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
// Only use previewDataHtml if preview call was made
|
|
2062
|
-
if (hasPreviewCallBeenMade && previewDataHtml?.resolvedBody) {
|
|
2063
|
-
resolvedContent = previewDataHtml.resolvedBody;
|
|
2064
|
-
|
|
2065
|
-
// Handle case where resolvedBody might be a JSON string
|
|
2066
|
-
if (typeof resolvedContent === 'string') {
|
|
2067
|
-
try {
|
|
2068
|
-
resolvedContent = JSON.parse(resolvedContent);
|
|
2069
|
-
} catch (e) {
|
|
2070
|
-
// If parsing fails, treat as null so we fall back to content
|
|
2071
|
-
resolvedContent = null;
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
2075
|
-
|
|
2076
|
-
// Parse content if it's a string
|
|
2440
|
+
// rcsCarouselContentObj is a useMemo that handles both the API-response path and the
|
|
2441
|
+
// client-side fallback. It is stable across keystrokes (only recomputes when
|
|
2442
|
+
// hasPreviewCallBeenMade / previewDataHtml / content / formData change).
|
|
2077
2443
|
let parsedRcsContent = content;
|
|
2078
2444
|
if (typeof content === 'string') {
|
|
2079
|
-
try {
|
|
2080
|
-
parsedRcsContent = JSON.parse(content);
|
|
2081
|
-
} catch (e) {
|
|
2082
|
-
parsedRcsContent = content;
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
// Use resolvedContent if available (from preview call), otherwise use raw content
|
|
2087
|
-
if (resolvedContent && typeof resolvedContent === 'object') {
|
|
2088
|
-
contentObj = { ...resolvedContent };
|
|
2089
|
-
} else {
|
|
2090
|
-
// Use raw content if no preview call was made or resolvedContent is not available
|
|
2091
|
-
contentObj = parsedRcsContent || {};
|
|
2445
|
+
try { parsedRcsContent = JSON.parse(content); } catch (e) { parsedRcsContent = content; }
|
|
2092
2446
|
}
|
|
2447
|
+
contentObj = rcsCarouselContentObj || parsedRcsContent || {};
|
|
2093
2448
|
} else if (channel === CHANNELS.INAPP) {
|
|
2094
2449
|
// For InApp, content is an object with androidContent and iosContent (similar to MobilePush)
|
|
2095
2450
|
// Content comes from InApp component state, passed via content prop
|
|
@@ -2190,39 +2545,33 @@ const CommonTestAndPreview = (props) => {
|
|
|
2190
2545
|
// Only use previewDataHtml if preview call was made
|
|
2191
2546
|
if (hasPreviewCallBeenMade && previewDataHtml?.resolvedBody) {
|
|
2192
2547
|
resolvedContent = previewDataHtml.resolvedBody;
|
|
2193
|
-
if (typeof resolvedContent === 'string') {
|
|
2194
|
-
try {
|
|
2195
|
-
resolvedContent = JSON.parse(resolvedContent);
|
|
2196
|
-
} catch (e) {
|
|
2197
|
-
resolvedContent = null;
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
2548
|
}
|
|
2201
2549
|
|
|
2202
|
-
// Parse content if it's a string
|
|
2203
|
-
let parsedViberContent =
|
|
2204
|
-
if (
|
|
2205
|
-
|
|
2550
|
+
// Parse content if it's a string
|
|
2551
|
+
let parsedViberContent = content;
|
|
2552
|
+
if (typeof content === 'string') {
|
|
2553
|
+
try {
|
|
2554
|
+
parsedViberContent = JSON.parse(content);
|
|
2555
|
+
} catch (e) {
|
|
2556
|
+
parsedViberContent = {};
|
|
2557
|
+
}
|
|
2206
2558
|
}
|
|
2207
|
-
//
|
|
2559
|
+
// Use resolvedContent if available (from preview call), otherwise use raw content
|
|
2208
2560
|
if (resolvedContent && typeof resolvedContent === 'object') {
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
if (
|
|
2212
|
-
contentObj.accountName =
|
|
2561
|
+
contentObj = { ...resolvedContent };
|
|
2562
|
+
// Merge in accountName and brandName from raw content if not in resolvedContent
|
|
2563
|
+
if (parsedViberContent?.accountName && !contentObj.accountName) {
|
|
2564
|
+
contentObj.accountName = parsedViberContent.accountName;
|
|
2213
2565
|
}
|
|
2214
|
-
if (
|
|
2215
|
-
contentObj.brandName =
|
|
2566
|
+
if (parsedViberContent?.brandName && !contentObj.brandName) {
|
|
2567
|
+
contentObj.brandName = parsedViberContent.brandName;
|
|
2216
2568
|
}
|
|
2217
2569
|
} else {
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
&& Object.keys(viberPreviewTagValues).length > 0
|
|
2224
|
-
) {
|
|
2225
|
-
contentObj = applyViberCustomValuesToContent(contentObj, viberPreviewTagValues);
|
|
2570
|
+
// Use raw content if no preview call was made or resolvedContent is not available
|
|
2571
|
+
contentObj = {...parsedViberContent, messageContent: resolvedContent};
|
|
2572
|
+
if (resolvedContent) {
|
|
2573
|
+
contentObj.viberPreviewContent = {...parsedViberContent?.viberPreviewContent, messageContent: resolvedContent};
|
|
2574
|
+
}
|
|
2226
2575
|
}
|
|
2227
2576
|
} else if (channel === CHANNELS.ZALO) {
|
|
2228
2577
|
// For Zalo, content is an object with templatePreviewUrl, templateStatus, etc.
|
|
@@ -2298,6 +2647,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
2298
2647
|
formatMessage,
|
|
2299
2648
|
lastModified: formData?.lastModified,
|
|
2300
2649
|
updatedByName: formData?.updatedByName,
|
|
2650
|
+
smsFallbackContent: isRcsSmsFallbackPreviewEnabled ? smsFallbackContent : null,
|
|
2651
|
+
smsFallbackResolvedText,
|
|
2652
|
+
activePreviewTab,
|
|
2653
|
+
onPreviewTabChange: setActivePreviewTab,
|
|
2301
2654
|
};
|
|
2302
2655
|
};
|
|
2303
2656
|
|
|
@@ -2337,7 +2690,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
2337
2690
|
}, [show, beeInstance, currentTab, channel]);
|
|
2338
2691
|
|
|
2339
2692
|
/**
|
|
2340
|
-
* Initial data load when slidebox opens
|
|
2693
|
+
* Initial data load when slidebox opens.
|
|
2694
|
+
* EXTRACT TAGS CALL SITES (on open/edit RCS):
|
|
2695
|
+
* 1. Here (non-email): actions.extractTagsRequested() at line ~2161 when show is true.
|
|
2696
|
+
* 2. RCS SMS fallback useEffect below: Api.extractTagsWithMetaData() for fallback message.
|
|
2697
|
+
* 3. handleExtractTags() (user clicks "Enter custom values for tags") – not from effects.
|
|
2698
|
+
* The "Process extracted tags" effect only processes API results and must not call extract again.
|
|
2341
2699
|
*/
|
|
2342
2700
|
useEffect(() => {
|
|
2343
2701
|
if (show) {
|
|
@@ -2422,7 +2780,61 @@ const CommonTestAndPreview = (props) => {
|
|
|
2422
2780
|
actions.getTestGroupsRequested();
|
|
2423
2781
|
}
|
|
2424
2782
|
}
|
|
2425
|
-
|
|
2783
|
+
// getCurrentContent: RCS applies cardVarMapped → placeholder resolution; re-extract when it changes.
|
|
2784
|
+
}, [show, beeInstance, currentTab, channel, getCurrentContent]);
|
|
2785
|
+
|
|
2786
|
+
/**
|
|
2787
|
+
* RCS with SMS fallback: extract tags for fallback SMS content as well
|
|
2788
|
+
* (so we can show a separate "Fallback SMS tags" section in left panel).
|
|
2789
|
+
*/
|
|
2790
|
+
useEffect(() => {
|
|
2791
|
+
let cancelled = false;
|
|
2792
|
+
|
|
2793
|
+
if (!show || channel !== CHANNELS.RCS) {
|
|
2794
|
+
return () => {
|
|
2795
|
+
cancelled = true;
|
|
2796
|
+
};
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
if (!smsFallbackContent?.templateContent && !smsFallbackContent?.content) {
|
|
2800
|
+
setSmsFallbackExtractedTags([]);
|
|
2801
|
+
setSmsFallbackRequiredTags([]);
|
|
2802
|
+
setSmsFallbackOptionalTags([]);
|
|
2803
|
+
return () => {
|
|
2804
|
+
cancelled = true;
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
setIsExtractingSmsFallbackTags(true);
|
|
2809
|
+
(async () => {
|
|
2810
|
+
try {
|
|
2811
|
+
const fallbackBodyForExtractApi = getSmsFallbackTextForTagExtraction(smsFallbackContent);
|
|
2812
|
+
const payload = {
|
|
2813
|
+
messageTitle: '',
|
|
2814
|
+
messageBody: fallbackBodyForExtractApi || '',
|
|
2815
|
+
};
|
|
2816
|
+
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
|
|
2817
|
+
let smsFallbackTagTree = response?.data ?? [];
|
|
2818
|
+
if (!Array.isArray(smsFallbackTagTree)) smsFallbackTagTree = [];
|
|
2819
|
+
if (!smsTemplateHasMustacheTags(fallbackBodyForExtractApi)) {
|
|
2820
|
+
smsFallbackTagTree = [];
|
|
2821
|
+
} else if (smsFallbackTagTree.length === 0) {
|
|
2822
|
+
smsFallbackTagTree = buildSyntheticSmsMustacheTags(fallbackBodyForExtractApi);
|
|
2823
|
+
}
|
|
2824
|
+
if (cancelled) return;
|
|
2825
|
+
setSmsFallbackExtractedTags(smsFallbackTagTree);
|
|
2826
|
+
} catch (e) {
|
|
2827
|
+
if (cancelled) return;
|
|
2828
|
+
setSmsFallbackExtractedTags([]);
|
|
2829
|
+
} finally {
|
|
2830
|
+
if (!cancelled) setIsExtractingSmsFallbackTags(false);
|
|
2831
|
+
}
|
|
2832
|
+
})();
|
|
2833
|
+
|
|
2834
|
+
return () => {
|
|
2835
|
+
cancelled = true;
|
|
2836
|
+
};
|
|
2837
|
+
}, [show, channel, smsFallbackContent]);
|
|
2426
2838
|
|
|
2427
2839
|
/**
|
|
2428
2840
|
* Email-specific: Handle content updates for both BEE and CKEditor
|
|
@@ -2474,17 +2886,22 @@ const CommonTestAndPreview = (props) => {
|
|
|
2474
2886
|
setSelectedCustomer(null);
|
|
2475
2887
|
setRequiredTags([]);
|
|
2476
2888
|
setOptionalTags([]);
|
|
2889
|
+
setSmsFallbackExtractedTags([]);
|
|
2890
|
+
setSmsFallbackRequiredTags([]);
|
|
2891
|
+
setSmsFallbackOptionalTags([]);
|
|
2892
|
+
setIsExtractingSmsFallbackTags(false);
|
|
2477
2893
|
setCustomValues({});
|
|
2478
2894
|
setShowJSON(false);
|
|
2479
2895
|
setTagsExtracted(false);
|
|
2480
2896
|
setPreviewDevice(DESKTOP);
|
|
2481
|
-
|
|
2482
|
-
|
|
2897
|
+
setActivePreviewTab(PREVIEW_TAB_RCS);
|
|
2898
|
+
setSmsFallbackPreviewText(undefined);
|
|
2483
2899
|
setSelectedTestEntities([]);
|
|
2484
2900
|
actions.clearPrefilledValues();
|
|
2485
2901
|
} else {
|
|
2486
2902
|
// Reset device to initialDevice when opening (Android for mobile channels, Desktop for others)
|
|
2487
2903
|
setPreviewDevice(initialDevice);
|
|
2904
|
+
setActivePreviewTab(PREVIEW_TAB_RCS);
|
|
2488
2905
|
}
|
|
2489
2906
|
}, [show, initialDevice]);
|
|
2490
2907
|
|
|
@@ -2493,79 +2910,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
2493
2910
|
*/
|
|
2494
2911
|
useEffect(() => {
|
|
2495
2912
|
if (previewData) {
|
|
2496
|
-
setPreviewDataHtml(previewData);
|
|
2913
|
+
setPreviewDataHtml(toPlainPreviewData(previewData));
|
|
2497
2914
|
}
|
|
2498
2915
|
}, [previewData]);
|
|
2499
2916
|
|
|
2500
|
-
/**
|
|
2501
|
-
* Process extracted tags and categorize them
|
|
2502
|
-
*/
|
|
2503
|
-
useEffect(() => {
|
|
2504
|
-
// Categorize tags into required and optional
|
|
2505
|
-
const required = [];
|
|
2506
|
-
const optional = [];
|
|
2507
|
-
let hasPersonalizationTags = false;
|
|
2508
|
-
|
|
2509
|
-
if (extractedTags?.length > 0) {
|
|
2510
|
-
const processTag = (tag, parentPath = '') => {
|
|
2511
|
-
const currentPath = parentPath ? `${parentPath}.${tag.name}` : tag.name;
|
|
2512
|
-
|
|
2513
|
-
// Skip unsubscribe tag for input fields
|
|
2514
|
-
if (tag?.name === UNSUBSCRIBE_TAG_NAME) {
|
|
2515
|
-
return;
|
|
2516
|
-
}
|
|
2517
|
-
|
|
2518
|
-
hasPersonalizationTags = true;
|
|
2519
|
-
|
|
2520
|
-
if (tag?.metaData?.userDriven === false) {
|
|
2521
|
-
required.push({
|
|
2522
|
-
...tag,
|
|
2523
|
-
fullPath: currentPath,
|
|
2524
|
-
});
|
|
2525
|
-
} else if (tag?.metaData?.userDriven === true) {
|
|
2526
|
-
optional.push({
|
|
2527
|
-
...tag,
|
|
2528
|
-
fullPath: currentPath,
|
|
2529
|
-
});
|
|
2530
|
-
}
|
|
2531
|
-
|
|
2532
|
-
if (tag?.children?.length > 0) {
|
|
2533
|
-
tag.children.forEach((child) => processTag(child, currentPath));
|
|
2534
|
-
}
|
|
2535
|
-
};
|
|
2536
|
-
|
|
2537
|
-
extractedTags.forEach((tag) => processTag(tag));
|
|
2538
|
-
|
|
2539
|
-
if (hasPersonalizationTags) {
|
|
2540
|
-
setRequiredTags(required);
|
|
2541
|
-
setOptionalTags(optional);
|
|
2542
|
-
setTagsExtracted(true); // Mark tags as extracted and processed
|
|
2543
|
-
|
|
2544
|
-
// Initialize custom values for required tags
|
|
2545
|
-
const initialValues = {};
|
|
2546
|
-
required.forEach((tag) => {
|
|
2547
|
-
initialValues[tag?.fullPath] = '';
|
|
2548
|
-
});
|
|
2549
|
-
optional.forEach((tag) => {
|
|
2550
|
-
initialValues[tag?.fullPath] = '';
|
|
2551
|
-
});
|
|
2552
|
-
setCustomValues(initialValues);
|
|
2553
|
-
} else {
|
|
2554
|
-
// Reset all tag-related state if no personalization tags
|
|
2555
|
-
setRequiredTags([]);
|
|
2556
|
-
setOptionalTags([]);
|
|
2557
|
-
setCustomValues({});
|
|
2558
|
-
setTagsExtracted(false);
|
|
2559
|
-
}
|
|
2560
|
-
} else {
|
|
2561
|
-
// Reset all tag-related state if no tags
|
|
2562
|
-
setRequiredTags([]);
|
|
2563
|
-
setOptionalTags([]);
|
|
2564
|
-
setCustomValues({});
|
|
2565
|
-
setTagsExtracted(false);
|
|
2566
|
-
}
|
|
2567
|
-
}, [extractedTags]);
|
|
2568
|
-
|
|
2569
2917
|
/**
|
|
2570
2918
|
* Handle customer selection and fetch prefilled values
|
|
2571
2919
|
*/
|
|
@@ -2573,17 +2921,15 @@ const CommonTestAndPreview = (props) => {
|
|
|
2573
2921
|
if (selectedCustomer && config.enableCustomerSearch !== false) {
|
|
2574
2922
|
setTagsExtracted(true); // Auto-open custom values editor
|
|
2575
2923
|
|
|
2576
|
-
// Get all available tags
|
|
2577
|
-
const allTags = [...requiredTags, ...optionalTags];
|
|
2578
2924
|
const requiredTagObj = {};
|
|
2579
|
-
|
|
2925
|
+
allRequiredTags.forEach((tag) => {
|
|
2580
2926
|
requiredTagObj[tag?.fullPath] = '';
|
|
2581
2927
|
});
|
|
2582
2928
|
if (allTags.length > 0) {
|
|
2583
2929
|
const payload = preparePreviewPayload(
|
|
2584
|
-
|
|
2930
|
+
activeChannelForActions,
|
|
2585
2931
|
formData || {},
|
|
2586
|
-
|
|
2932
|
+
activeContentForActions,
|
|
2587
2933
|
{
|
|
2588
2934
|
...requiredTagObj,
|
|
2589
2935
|
},
|
|
@@ -2592,7 +2938,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2592
2938
|
actions.getPrefilledValuesRequested(payload);
|
|
2593
2939
|
}
|
|
2594
2940
|
}
|
|
2595
|
-
}, [selectedCustomer]);
|
|
2941
|
+
}, [selectedCustomer, allTags.length, activeChannelForActions, activePreviewTab]);
|
|
2596
2942
|
|
|
2597
2943
|
/**
|
|
2598
2944
|
* Update custom values with prefilled values from API
|
|
@@ -2603,29 +2949,29 @@ const CommonTestAndPreview = (props) => {
|
|
|
2603
2949
|
if (prefilledValues && selectedCustomer) {
|
|
2604
2950
|
// Always replace all values with prefilled values
|
|
2605
2951
|
const updatedValues = {};
|
|
2606
|
-
|
|
2952
|
+
allTags.forEach((tag) => {
|
|
2607
2953
|
updatedValues[tag?.fullPath] = prefilledValues[tag?.fullPath] || '';
|
|
2608
2954
|
});
|
|
2609
2955
|
|
|
2610
2956
|
setCustomValues(updatedValues);
|
|
2611
2957
|
|
|
2612
|
-
// Viber: do not auto-resolve tags in preview; user must click Update preview
|
|
2613
|
-
if (channel === CHANNELS.VIBER) {
|
|
2614
|
-
return;
|
|
2615
|
-
}
|
|
2616
|
-
|
|
2617
2958
|
// Update preview with prefilled values (this is a valid preview call trigger)
|
|
2959
|
+
// For RCS: always dispatch with RCS channel/content so previewDataHtml stays RCS-shaped.
|
|
2960
|
+
// SMS fallback preview is kept in sync by syncSmsFallbackPreview via smsFallbackPreviewText.
|
|
2961
|
+
const previewChannelForPrefill = channel === CHANNELS.RCS ? CHANNELS.RCS : activeChannelForActions;
|
|
2962
|
+
const previewContentForPrefill = channel === CHANNELS.RCS ? getCurrentContent : activeContentForActions;
|
|
2618
2963
|
const payload = preparePreviewPayload(
|
|
2619
|
-
|
|
2964
|
+
previewChannelForPrefill,
|
|
2620
2965
|
formData || {},
|
|
2621
|
-
|
|
2966
|
+
previewContentForPrefill,
|
|
2622
2967
|
updatedValues,
|
|
2623
2968
|
selectedCustomer
|
|
2624
2969
|
);
|
|
2625
2970
|
actions.updatePreviewRequested(payload);
|
|
2626
2971
|
setHasPreviewCallBeenMade(true); // Mark that preview call was made
|
|
2972
|
+
void syncSmsFallbackPreview(updatedValues, selectedCustomer);
|
|
2627
2973
|
}
|
|
2628
|
-
}, [JSON.stringify(prefilledValues), selectedCustomer]);
|
|
2974
|
+
}, [JSON.stringify(prefilledValues), selectedCustomer, activeChannelForActions, activePreviewTab]);
|
|
2629
2975
|
|
|
2630
2976
|
/**
|
|
2631
2977
|
* Map channel constants to display names (lowercase for message)
|
|
@@ -2691,7 +3037,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
2691
3037
|
setPreviewDevice(DESKTOP);
|
|
2692
3038
|
setPreviewDataHtml('');
|
|
2693
3039
|
setHasPreviewCallBeenMade(false); // Reset preview call flag
|
|
2694
|
-
setViberPreviewTagValues(null);
|
|
2695
3040
|
setSelectedTestEntities([]);
|
|
2696
3041
|
setBeeContent('');
|
|
2697
3042
|
previousBeeContentRef.current = '';
|
|
@@ -2720,11 +3065,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2720
3065
|
setTagsExtracted(true); // Auto-open custom values editor
|
|
2721
3066
|
|
|
2722
3067
|
// Clear any existing values while waiting for prefilled values
|
|
2723
|
-
|
|
2724
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2725
|
-
emptyValues[tag?.fullPath] = '';
|
|
2726
|
-
});
|
|
2727
|
-
setCustomValues(emptyValues);
|
|
3068
|
+
setCustomValues(buildEmptyValues());
|
|
2728
3069
|
};
|
|
2729
3070
|
|
|
2730
3071
|
/**
|
|
@@ -2733,17 +3074,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
2733
3074
|
const handleClearSelection = () => {
|
|
2734
3075
|
setSelectedCustomer(null);
|
|
2735
3076
|
setHasPreviewCallBeenMade(false); // Reset flag when customer is cleared
|
|
2736
|
-
setViberPreviewTagValues(null);
|
|
2737
3077
|
|
|
2738
3078
|
// Clear all preview errors when customer is cleared
|
|
2739
3079
|
actions.clearPreviewErrors();
|
|
2740
3080
|
|
|
2741
3081
|
// Initialize empty values for all tags
|
|
2742
|
-
|
|
2743
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2744
|
-
emptyValues[tag?.fullPath] = '';
|
|
2745
|
-
});
|
|
2746
|
-
setCustomValues(emptyValues);
|
|
3082
|
+
setCustomValues(buildEmptyValues());
|
|
2747
3083
|
|
|
2748
3084
|
// Don't make preview call when clearing selection - just reset to raw content
|
|
2749
3085
|
// Preview will be shown using raw formData/content
|
|
@@ -2779,23 +3115,23 @@ const CommonTestAndPreview = (props) => {
|
|
|
2779
3115
|
*/
|
|
2780
3116
|
const handleDiscardCustomValues = () => {
|
|
2781
3117
|
// Initialize empty values for all tags
|
|
2782
|
-
const emptyValues =
|
|
2783
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2784
|
-
emptyValues[tag?.fullPath] = '';
|
|
2785
|
-
});
|
|
3118
|
+
const emptyValues = buildEmptyValues();
|
|
2786
3119
|
setCustomValues(emptyValues);
|
|
2787
3120
|
|
|
3121
|
+
// Reset SMS fallback preview so it shows raw template (with {{tags}} visible) after discard
|
|
3122
|
+
setSmsFallbackPreviewText(undefined);
|
|
3123
|
+
|
|
2788
3124
|
// Update preview with empty values (this is a valid preview call trigger)
|
|
3125
|
+
// For RCS: always dispatch with RCS channel/content so previewDataHtml stays RCS-shaped.
|
|
3126
|
+
const previewChannelForDiscard = channel === CHANNELS.RCS ? CHANNELS.RCS : activeChannelForActions;
|
|
3127
|
+
const previewContentForDiscard = channel === CHANNELS.RCS ? getCurrentContent : activeContentForActions;
|
|
2789
3128
|
const payload = preparePreviewPayload(
|
|
2790
|
-
|
|
3129
|
+
previewChannelForDiscard,
|
|
2791
3130
|
formData || {},
|
|
2792
|
-
|
|
3131
|
+
previewContentForDiscard,
|
|
2793
3132
|
emptyValues,
|
|
2794
3133
|
selectedCustomer
|
|
2795
3134
|
);
|
|
2796
|
-
if (channel === CHANNELS.VIBER) {
|
|
2797
|
-
setViberPreviewTagValues({ ...emptyValues });
|
|
2798
|
-
}
|
|
2799
3135
|
actions.updatePreviewRequested(payload);
|
|
2800
3136
|
setHasPreviewCallBeenMade(true); // Mark that preview call was made
|
|
2801
3137
|
};
|
|
@@ -2806,17 +3142,24 @@ const CommonTestAndPreview = (props) => {
|
|
|
2806
3142
|
*/
|
|
2807
3143
|
const handleUpdatePreview = async () => {
|
|
2808
3144
|
try {
|
|
3145
|
+
// Capture customValues at click time so the carousel preview only updates here,
|
|
3146
|
+
// not on every subsequent keystroke.
|
|
3147
|
+
previewCustomValuesRef.current = customValues;
|
|
3148
|
+
// For RCS: always dispatch with RCS channel/content so previewDataHtml stays RCS-shaped,
|
|
3149
|
+
// even when the user triggers update from the SMS fallback tab.
|
|
3150
|
+
// SMS fallback preview is kept in sync by syncSmsFallbackPreview via smsFallbackPreviewText.
|
|
3151
|
+
const previewChannel = channel === CHANNELS.RCS ? CHANNELS.RCS : activeChannelForActions;
|
|
3152
|
+
const previewContent = channel === CHANNELS.RCS ? getCurrentContent : activeContentForActions;
|
|
2809
3153
|
const payload = preparePreviewPayload(
|
|
2810
|
-
|
|
3154
|
+
previewChannel,
|
|
2811
3155
|
formData || {},
|
|
2812
|
-
|
|
3156
|
+
previewContent,
|
|
2813
3157
|
customValues,
|
|
2814
3158
|
selectedCustomer
|
|
2815
3159
|
);
|
|
2816
|
-
if (channel === CHANNELS.VIBER) {
|
|
2817
|
-
setViberPreviewTagValues({ ...customValues });
|
|
2818
|
-
}
|
|
2819
3160
|
await actions.updatePreviewRequested(payload);
|
|
3161
|
+
|
|
3162
|
+
await syncSmsFallbackPreview(customValues, selectedCustomer);
|
|
2820
3163
|
setHasPreviewCallBeenMade(true); // Mark that preview call was made
|
|
2821
3164
|
} catch (error) {
|
|
2822
3165
|
CapNotification.error({
|
|
@@ -2826,27 +3169,115 @@ const CommonTestAndPreview = (props) => {
|
|
|
2826
3169
|
};
|
|
2827
3170
|
|
|
2828
3171
|
/**
|
|
2829
|
-
*
|
|
3172
|
+
* Categorize extracted tags into required/optional.
|
|
2830
3173
|
*/
|
|
2831
|
-
const
|
|
2832
|
-
|
|
2833
|
-
|
|
3174
|
+
const categorizeTags = (tagsTree = []) => {
|
|
3175
|
+
const required = [];
|
|
3176
|
+
const optional = [];
|
|
3177
|
+
let hasPersonalizationTags = false;
|
|
3178
|
+
const processTag = (tag, parentPath = '') => {
|
|
3179
|
+
const currentPath = parentPath ? `${parentPath}.${tag.name}` : tag.name;
|
|
3180
|
+
|
|
3181
|
+
// Skip unsubscribe tag for input fields
|
|
3182
|
+
if (tag?.name === UNSUBSCRIBE_TAG_NAME) return;
|
|
3183
|
+
|
|
3184
|
+
hasPersonalizationTags = true;
|
|
3185
|
+
const userDriven = tag?.metaData?.userDriven;
|
|
3186
|
+
if (userDriven === true) {
|
|
3187
|
+
optional.push({ ...tag, fullPath: currentPath });
|
|
3188
|
+
} else {
|
|
3189
|
+
// false or missing (SMS/DLT extract often omits metaData) → required for test values
|
|
3190
|
+
required.push({ ...tag, fullPath: currentPath });
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
if (tag?.children?.length > 0) {
|
|
3194
|
+
tag.children.forEach((child) => processTag(child, currentPath));
|
|
3195
|
+
}
|
|
3196
|
+
};
|
|
2834
3197
|
|
|
3198
|
+
(tagsTree || []).forEach((tag) => processTag(tag));
|
|
3199
|
+
return { required, optional, hasPersonalizationTags };
|
|
3200
|
+
};
|
|
3201
|
+
|
|
3202
|
+
/**
|
|
3203
|
+
* Apply tag extraction when content comes from RCS + SMS fallback (no API call).
|
|
3204
|
+
*/
|
|
3205
|
+
const applyRcsSmsFallbackTagExtraction = () => {
|
|
3206
|
+
const rcsPrimaryCategorized = categorizeTags(extractedTags ?? []);
|
|
3207
|
+
const fallbackSmsResolvedForTags = smsFallbackTextForTagExtraction ?? '';
|
|
3208
|
+
let fallbackSmsTagTree = smsFallbackExtractedTags?.length > 0
|
|
3209
|
+
? smsFallbackExtractedTags
|
|
3210
|
+
: buildSyntheticSmsMustacheTags(fallbackSmsResolvedForTags);
|
|
3211
|
+
if (!smsTemplateHasMustacheTags(fallbackSmsResolvedForTags)) {
|
|
3212
|
+
fallbackSmsTagTree = [];
|
|
3213
|
+
}
|
|
3214
|
+
const fallbackSmsCategorized = categorizeTags(fallbackSmsTagTree);
|
|
3215
|
+
setRequiredTags(rcsPrimaryCategorized.required);
|
|
3216
|
+
setOptionalTags(rcsPrimaryCategorized.optional);
|
|
3217
|
+
setSmsFallbackRequiredTags(fallbackSmsCategorized.required);
|
|
3218
|
+
setSmsFallbackOptionalTags(fallbackSmsCategorized.optional);
|
|
3219
|
+
setTagsExtracted(
|
|
3220
|
+
rcsPrimaryCategorized.hasPersonalizationTags || fallbackSmsCategorized.hasPersonalizationTags,
|
|
3221
|
+
);
|
|
3222
|
+
setCustomValues((prev) => mergeCustomValuesWithTagKeys(prev, rcsPrimaryCategorized, fallbackSmsCategorized));
|
|
3223
|
+
};
|
|
3224
|
+
|
|
3225
|
+
/**
|
|
3226
|
+
* When extract-tags API returns, map Redux `extractedTags` into required/optional so
|
|
3227
|
+
* CustomValuesEditor shows personalization fields (effect was previously commented out).
|
|
3228
|
+
* RCS + SMS fallback: merge primary + fallback tag trees when fallback template exists.
|
|
3229
|
+
*/
|
|
3230
|
+
useEffect(() => {
|
|
3231
|
+
if (!show) return;
|
|
3232
|
+
const hasFallbackSmsTemplate = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
3233
|
+
if (channel === CHANNELS.RCS && hasFallbackSmsTemplate) {
|
|
3234
|
+
applyRcsSmsFallbackTagExtraction();
|
|
3235
|
+
return;
|
|
3236
|
+
}
|
|
3237
|
+
const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
|
|
3238
|
+
let smsTagSource = channel === CHANNELS.SMS && (!extractedTags || extractedTags.length === 0)
|
|
3239
|
+
? buildSyntheticSmsMustacheTags(smsEditorBody)
|
|
3240
|
+
: (extractedTags ?? []);
|
|
3241
|
+
if (channel === CHANNELS.SMS && !smsTemplateHasMustacheTags(smsEditorBody)) {
|
|
3242
|
+
smsTagSource = [];
|
|
3243
|
+
}
|
|
3244
|
+
const { required, optional, hasPersonalizationTags } = categorizeTags(smsTagSource);
|
|
3245
|
+
setRequiredTags(required);
|
|
3246
|
+
setOptionalTags(optional);
|
|
3247
|
+
setTagsExtracted(hasPersonalizationTags);
|
|
3248
|
+
setCustomValues((prev) => mergeCustomValuesWithTagKeys(prev, { required, optional }, { required: [], optional: [] }));
|
|
3249
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- applyRcsSmsFallbackTagExtraction closes over latest extractedTags/smsFallbackExtractedTags
|
|
3250
|
+
}, [show, extractedTags, channel, smsFallbackContent, smsFallbackExtractedTags, getCurrentContent, smsFallbackTextForTagExtraction]);
|
|
3251
|
+
|
|
3252
|
+
/**
|
|
3253
|
+
* Get content to run tag extraction on (channel-specific).
|
|
3254
|
+
*/
|
|
3255
|
+
const getContentForTagExtraction = () => {
|
|
3256
|
+
let contentToExtract = activeContentForActions;
|
|
2835
3257
|
if (channel === CHANNELS.EMAIL && formData) {
|
|
2836
3258
|
const currentTabData = formData[currentTab - 1];
|
|
2837
3259
|
const activeTab = currentTabData?.activeTab;
|
|
2838
3260
|
const templateContent = currentTabData?.[activeTab]?.['template-content'];
|
|
2839
3261
|
contentToExtract = templateContent || contentToExtract;
|
|
2840
|
-
} else if (channel === CHANNELS.VIBER) {
|
|
2841
|
-
contentToExtract = getViberTagExtractionContent(getViberMergedFormData(), contentToExtract);
|
|
2842
3262
|
}
|
|
3263
|
+
return contentToExtract;
|
|
3264
|
+
};
|
|
2843
3265
|
|
|
2844
|
-
|
|
3266
|
+
/**
|
|
3267
|
+
* Handle extract tags
|
|
3268
|
+
*/
|
|
3269
|
+
const handleExtractTags = () => {
|
|
3270
|
+
if (channel === CHANNELS.RCS) {
|
|
3271
|
+
applyRcsSmsFallbackTagExtraction();
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
const contentToExtract = getContentForTagExtraction();
|
|
2845
3276
|
const tags = contentToExtract.match(/{{[^}]+}}/g) || [];
|
|
2846
3277
|
const hasPersonalizationTags = tags.some((tag) => !tag.includes(UNSUBSCRIBE_TAG_NAME));
|
|
3278
|
+
const onlyUnsubscribe = !hasPersonalizationTags && tags.length === 1 && tags[0].includes(UNSUBSCRIBE_TAG_NAME);
|
|
2847
3279
|
|
|
2848
|
-
if (
|
|
2849
|
-
// If only unsubscribe tag is present, show noTagsExtracted message
|
|
3280
|
+
if (onlyUnsubscribe) {
|
|
2850
3281
|
setTagsExtracted(false);
|
|
2851
3282
|
setRequiredTags([]);
|
|
2852
3283
|
setOptionalTags([]);
|
|
@@ -2854,10 +3285,9 @@ const CommonTestAndPreview = (props) => {
|
|
|
2854
3285
|
return;
|
|
2855
3286
|
}
|
|
2856
3287
|
|
|
2857
|
-
// Extract tags
|
|
2858
3288
|
setTagsExtracted(true);
|
|
2859
3289
|
const { templateSubject, templateContent } = prepareTagExtractionPayload(
|
|
2860
|
-
|
|
3290
|
+
activeChannelForActions,
|
|
2861
3291
|
formData || {},
|
|
2862
3292
|
contentToExtract
|
|
2863
3293
|
);
|
|
@@ -2919,7 +3349,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2919
3349
|
if (existingTestCustomer) {
|
|
2920
3350
|
const entityId = existingTestCustomer.userId ?? existingTestCustomer.customerId;
|
|
2921
3351
|
if (entityId != null) {
|
|
2922
|
-
const id =
|
|
3352
|
+
const id = normalizeTestEntityId(entityId);
|
|
2923
3353
|
setSelectedTestEntities((prev) => (
|
|
2924
3354
|
prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
|
|
2925
3355
|
));
|
|
@@ -2956,7 +3386,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2956
3386
|
(c) => String(c?.customerId) === customerIdFromLookup || String(c?.userId) === customerIdFromLookup
|
|
2957
3387
|
);
|
|
2958
3388
|
if (alreadyInTestListByCustomerId) {
|
|
2959
|
-
const id =
|
|
3389
|
+
const id = normalizeTestEntityId(customerIdFromLookup);
|
|
2960
3390
|
setSelectedTestEntities((prev) => (
|
|
2961
3391
|
prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
|
|
2962
3392
|
));
|
|
@@ -2993,26 +3423,21 @@ const CommonTestAndPreview = (props) => {
|
|
|
2993
3423
|
const handleSendTestMessage = () => {
|
|
2994
3424
|
const allUserIds = [];
|
|
2995
3425
|
selectedTestEntities.forEach((entityId) => {
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
if (group) {
|
|
3000
|
-
allUserIds.push(...group.userIds);
|
|
3001
|
-
}
|
|
3426
|
+
const group = testGroups.find((g) => testEntityIdsEqual(g.groupId, entityId));
|
|
3427
|
+
if (group) {
|
|
3428
|
+
allUserIds.push(...group.userIds);
|
|
3002
3429
|
} else {
|
|
3003
|
-
|
|
3004
|
-
? String(entityId).slice('customer:'.length)
|
|
3005
|
-
: String(entityId);
|
|
3006
|
-
allUserIds.push(Number(rawId));
|
|
3430
|
+
allUserIds.push(entityId);
|
|
3007
3431
|
}
|
|
3008
3432
|
});
|
|
3009
3433
|
const uniqueUserIds = [...new Set(allUserIds)];
|
|
3010
3434
|
|
|
3011
|
-
const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP].includes(channel)
|
|
3435
|
+
const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS].includes(channel)
|
|
3012
3436
|
? testPreviewDeliverySettings[channel]
|
|
3013
3437
|
: null;
|
|
3014
3438
|
|
|
3015
|
-
//
|
|
3439
|
+
// createMessageMeta must match the creative channel and full creative (RCS + SMS fallback in one meta).
|
|
3440
|
+
// Do not use activeChannelForActions / activeContentForActions — those follow the RCS vs Fallback SMS *preview* tab.
|
|
3016
3441
|
const initialPayload = prepareTestMessagePayload(
|
|
3017
3442
|
channel,
|
|
3018
3443
|
formData || content || {},
|
|
@@ -3020,7 +3445,8 @@ const CommonTestAndPreview = (props) => {
|
|
|
3020
3445
|
customValues,
|
|
3021
3446
|
uniqueUserIds,
|
|
3022
3447
|
previewData,
|
|
3023
|
-
deliveryOverride
|
|
3448
|
+
deliveryOverride,
|
|
3449
|
+
{},
|
|
3024
3450
|
);
|
|
3025
3451
|
|
|
3026
3452
|
actions.createMessageMetaRequested(
|
|
@@ -3053,11 +3479,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
3053
3479
|
// ============================================
|
|
3054
3480
|
// RENDER HELPER FUNCTIONS
|
|
3055
3481
|
// ============================================
|
|
3056
|
-
|
|
3057
3482
|
const renderLeftPanelContent = () => (
|
|
3058
3483
|
<LeftPanelContent
|
|
3059
|
-
isExtractingTags={isExtractingTags}
|
|
3060
|
-
extractedTags={
|
|
3484
|
+
isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
|
|
3485
|
+
extractedTags={leftPanelExtractedTags}
|
|
3061
3486
|
selectedCustomer={selectedCustomer}
|
|
3062
3487
|
handleCustomerSelect={handleCustomerSelect}
|
|
3063
3488
|
handleSearchCustomer={handleSearchCustomer}
|
|
@@ -3075,15 +3500,29 @@ const CommonTestAndPreview = (props) => {
|
|
|
3075
3500
|
|
|
3076
3501
|
const renderCustomValuesEditor = () => (
|
|
3077
3502
|
<CustomValuesEditor
|
|
3078
|
-
isExtractingTags={isExtractingTags}
|
|
3503
|
+
isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
|
|
3079
3504
|
isUpdatePreviewDisabled={isUpdatePreviewDisabled}
|
|
3080
3505
|
showJSON={showJSON}
|
|
3081
3506
|
setShowJSON={setShowJSON}
|
|
3082
3507
|
customValues={customValues}
|
|
3083
3508
|
handleJSONTextChange={handleJSONTextChange}
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3509
|
+
sections={[
|
|
3510
|
+
{
|
|
3511
|
+
key: channel,
|
|
3512
|
+
title:
|
|
3513
|
+
channel === CHANNELS.RCS
|
|
3514
|
+
? messages.rcsTagsSectionTitle
|
|
3515
|
+
: messages[`${channel}TagsSectionTitle`],
|
|
3516
|
+
requiredTags,
|
|
3517
|
+
optionalTags,
|
|
3518
|
+
},
|
|
3519
|
+
{
|
|
3520
|
+
key: PREVIEW_TAB_SMS_FALLBACK,
|
|
3521
|
+
title: isRcsSmsFallbackPreviewEnabled ? messages.smsFallbackTagsSectionTitle : null,
|
|
3522
|
+
requiredTags: smsFallbackRequiredTags,
|
|
3523
|
+
optionalTags: smsFallbackOptionalTags,
|
|
3524
|
+
},
|
|
3525
|
+
]}
|
|
3087
3526
|
handleCustomValueChange={handleCustomValueChange}
|
|
3088
3527
|
handleDiscardCustomValues={handleDiscardCustomValues}
|
|
3089
3528
|
handleUpdatePreview={handleUpdatePreview}
|
|
@@ -3099,18 +3538,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
3099
3538
|
}));
|
|
3100
3539
|
};
|
|
3101
3540
|
|
|
3102
|
-
/** Trim pasted emails (trailing CR/LF).
|
|
3541
|
+
/** Trim pasted emails (trailing CR/LF). Allow any input for SMS; valid-only check is in renderAddTestCustomerButton. */
|
|
3103
3542
|
const handleTestCustomersSearch = useCallback((value) => {
|
|
3104
3543
|
if (value == null || value === '') {
|
|
3105
3544
|
setSearchValue('');
|
|
3106
3545
|
return;
|
|
3107
3546
|
}
|
|
3108
|
-
|
|
3109
|
-
if (channel === CHANNELS.SMS) {
|
|
3110
|
-
setSearchValue(formatPhoneNumber(raw));
|
|
3111
|
-
} else {
|
|
3112
|
-
setSearchValue(raw);
|
|
3113
|
-
}
|
|
3547
|
+
setSearchValue(String(value).trim());
|
|
3114
3548
|
}, [channel]);
|
|
3115
3549
|
|
|
3116
3550
|
const renderSendTestMessage = () => (
|
|
@@ -3128,12 +3562,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
3128
3562
|
renderAddTestCustomerButton={renderAddTestCustomerButton}
|
|
3129
3563
|
formatMessage={formatMessage}
|
|
3130
3564
|
deliverySettings={testPreviewDeliverySettings[channel]}
|
|
3131
|
-
|
|
3565
|
+
senderDetailsByChannel={senderDetailsByChannel}
|
|
3132
3566
|
wecrmAccounts={wecrmAccounts}
|
|
3133
3567
|
onSaveDeliverySettings={handleSaveDeliverySettings}
|
|
3134
3568
|
isLoadingSenderDetails={isLoadingSenderDetails}
|
|
3135
3569
|
smsTraiDltEnabled={smsTraiDltEnabled}
|
|
3136
3570
|
registeredSenderIds={registeredSenderIds}
|
|
3571
|
+
isChannelSmsFallbackPreviewEnabled={isRcsSmsFallbackPreviewEnabled}
|
|
3137
3572
|
searchValue={searchValue}
|
|
3138
3573
|
setSearchValue={handleTestCustomersSearch}
|
|
3139
3574
|
/>
|
|
@@ -3147,14 +3582,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
3147
3582
|
|
|
3148
3583
|
const renderAddTestCustomerButton = () => {
|
|
3149
3584
|
const raw = (searchValue || '').trim();
|
|
3150
|
-
const value = channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw;
|
|
3151
3585
|
const showAddButton =
|
|
3152
3586
|
[CHANNELS.EMAIL, CHANNELS.SMS].includes(channel) &&
|
|
3153
|
-
(channel === CHANNELS.EMAIL ? isValidEmail(
|
|
3587
|
+
(channel === CHANNELS.EMAIL ? isValidEmail(raw) : isValidMobile(formatPhoneNumber(raw)));
|
|
3154
3588
|
if (!showAddButton) return null;
|
|
3155
3589
|
return (
|
|
3156
3590
|
<AddTestCustomerButton
|
|
3157
|
-
searchValue={
|
|
3591
|
+
searchValue={channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw}
|
|
3158
3592
|
handleAddTestCustomer={handleAddTestCustomer}
|
|
3159
3593
|
/>
|
|
3160
3594
|
);
|