cap-creatives-ui 8.0.280 → 8.0.321
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/.github/workflows/pr-title-check.yml +88 -0
- package/app/constants/unified.js +21 -1
- package/app/containers/App/constants.js +0 -1
- package/app/containers/Login/test/index.test.js +123 -0
- package/app/containers/Login/test/selectors.test.js +165 -0
- package/app/initialState.js +0 -2
- package/app/services/api.js +6 -0
- package/app/services/tests/api.test.js +7 -0
- package/app/services/tests/getSchema.test.js +95 -0
- package/app/utils/common.js +23 -9
- package/app/utils/commonUtils.js +64 -93
- package/app/utils/tagValidations.js +83 -219
- package/app/utils/templateVarUtils.js +172 -0
- package/app/utils/tests/common.test.js +265 -323
- package/app/utils/tests/commonUtil.test.js +461 -118
- package/app/utils/tests/commonUtils.test.js +581 -0
- package/app/utils/tests/messageUtils.test.js +95 -0
- package/app/utils/tests/smsCharCount.test.js +304 -0
- package/app/utils/tests/smsCharCountV2.test.js +213 -10
- package/app/utils/tests/tagValidations.test.js +474 -357
- package/app/utils/tests/templateVarUtils.test.js +160 -0
- package/app/v2Components/CapDeviceContent/index.js +10 -7
- package/app/v2Components/CapTagList/index.js +32 -24
- package/app/v2Components/CapTagList/style.scss +48 -0
- package/app/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
- package/app/v2Components/CapTagListWithInput/index.js +8 -0
- package/app/v2Components/CapWhatsappCTA/index.js +2 -0
- package/app/v2Components/CapWhatsappCarouselButton/index.js +32 -14
- package/app/v2Components/CapWhatsappCarouselButton/tests/index.test.js +120 -2
- package/app/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +39 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +606 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.scss +36 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +79 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/index.js +314 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +141 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +156 -0
- package/app/v2Components/CommonTestAndPreview/SendTestMessage.js +57 -1
- package/app/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
- package/app/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
- package/app/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +210 -4
- package/app/v2Components/CommonTestAndPreview/actions.js +20 -0
- package/app/v2Components/CommonTestAndPreview/constants.js +57 -1
- package/app/v2Components/CommonTestAndPreview/index.js +878 -156
- package/app/v2Components/CommonTestAndPreview/messages.js +41 -3
- package/app/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/app/v2Components/CommonTestAndPreview/reducer.js +47 -0
- package/app/v2Components/CommonTestAndPreview/sagas.js +75 -5
- package/app/v2Components/CommonTestAndPreview/selectors.js +51 -0
- package/app/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
- package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +1156 -0
- package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +334 -0
- package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +576 -0
- package/app/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +156 -0
- package/app/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
- package/app/v2Components/CommonTestAndPreview/tests/actions.test.js +50 -0
- package/app/v2Components/CommonTestAndPreview/tests/constants.test.js +18 -7
- package/app/v2Components/CommonTestAndPreview/tests/index.test.js +914 -5
- package/app/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/app/v2Components/CommonTestAndPreview/tests/reducer.test.js +118 -0
- package/app/v2Components/CommonTestAndPreview/tests/sagas.test.js +146 -378
- package/app/v2Components/CommonTestAndPreview/tests/selectors.test.js +146 -0
- package/app/v2Components/ErrorInfoNote/index.js +24 -26
- package/app/v2Components/FormBuilder/index.js +182 -204
- package/app/v2Components/FormBuilder/messages.js +4 -8
- package/app/v2Components/HtmlEditor/HTMLEditor.js +7 -6
- package/app/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -1
- package/app/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +928 -17
- package/app/v2Components/HtmlEditor/components/CodeEditorPane/index.js +4 -2
- package/app/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +452 -3
- package/app/v2Components/HtmlEditor/hooks/useValidation.js +12 -9
- package/app/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +132 -0
- package/app/v2Components/HtmlEditor/utils/htmlValidator.js +4 -2
- package/app/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
- package/app/v2Components/SmsFallback/constants.js +73 -0
- package/app/v2Components/SmsFallback/index.js +956 -0
- package/app/v2Components/SmsFallback/index.scss +265 -0
- package/app/v2Components/SmsFallback/messages.js +78 -0
- package/app/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
- package/app/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
- package/app/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
- package/app/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
- package/app/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
- package/app/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
- package/app/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/app/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/app/v2Components/TestAndPreviewSlidebox/index.js +22 -1
- package/app/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
- package/app/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
- package/app/v2Components/VarSegmentMessageEditor/constants.js +2 -0
- package/app/v2Components/VarSegmentMessageEditor/index.js +125 -0
- package/app/v2Components/VarSegmentMessageEditor/index.scss +46 -0
- package/app/v2Containers/BeeEditor/index.js +3 -0
- package/app/v2Containers/BeePopupEditor/index.js +9 -2
- package/app/v2Containers/Cap/mockData.js +0 -14
- package/app/v2Containers/Cap/reducer.js +3 -55
- package/app/v2Containers/Cap/tests/reducer.test.js +0 -102
- package/app/v2Containers/CommunicationFlow/CommunicationFlow.js +291 -0
- package/app/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
- package/app/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
- package/app/v2Containers/CommunicationFlow/constants.js +200 -0
- package/app/v2Containers/CommunicationFlow/index.js +102 -0
- package/app/v2Containers/CommunicationFlow/messages.js +346 -0
- package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
- package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
- package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
- package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
- package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
- package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
- package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
- package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
- package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
- package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
- package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
- package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
- package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
- package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
- package/app/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
- package/app/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
- package/app/v2Containers/CreativesContainer/SlideBoxContent.js +127 -11
- package/app/v2Containers/CreativesContainer/SlideBoxFooter.js +62 -9
- package/app/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
- package/app/v2Containers/CreativesContainer/constants.js +24 -0
- package/app/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
- package/app/v2Containers/CreativesContainer/index.js +346 -71
- package/app/v2Containers/CreativesContainer/index.scss +51 -1
- package/app/v2Containers/CreativesContainer/messages.js +12 -0
- package/app/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/app/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +69 -1
- package/app/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +443 -0
- package/app/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
- package/app/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +147 -4
- package/app/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
- package/app/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +57 -10
- package/app/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/app/v2Containers/CreativesContainer/tests/index.test.js +71 -9
- package/app/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
- package/app/v2Containers/Email/index.js +2 -5
- package/app/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +58 -77
- package/app/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
- package/app/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +158 -89
- package/app/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
- package/app/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +17 -12
- package/app/v2Containers/EmailWrapper/index.js +4 -0
- package/app/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
- package/app/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +133 -0
- package/app/v2Containers/FTP/index.js +2 -51
- package/app/v2Containers/FTP/messages.js +0 -4
- package/app/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +110 -155
- package/app/v2Containers/InApp/index.js +297 -118
- package/app/v2Containers/InApp/tests/index.test.js +17 -6
- package/app/v2Containers/InApp/tests/mockData.js +1 -1
- package/app/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +19 -0
- package/app/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +3 -0
- package/app/v2Containers/InAppWrapper/index.js +3 -0
- package/app/v2Containers/InappAdvance/index.js +5 -104
- package/app/v2Containers/InappAdvance/tests/index.test.js +2 -0
- package/app/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +24 -3
- package/app/v2Containers/Line/Container/Text/index.js +0 -1
- package/app/v2Containers/MobilePush/Create/index.js +105 -28
- package/app/v2Containers/MobilePush/Create/messages.js +4 -0
- package/app/v2Containers/MobilePush/Edit/index.js +250 -68
- package/app/v2Containers/MobilePush/Edit/messages.js +4 -0
- package/app/v2Containers/MobilePushNew/components/PlatformContentFields.js +36 -12
- package/app/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +68 -27
- package/app/v2Containers/MobilePushNew/index.js +78 -35
- package/app/v2Containers/MobilePushNew/messages.js +8 -0
- package/app/v2Containers/MobilepushWrapper/index.js +11 -1
- package/app/v2Containers/Rcs/constants.js +32 -1
- package/app/v2Containers/Rcs/index.js +963 -916
- package/app/v2Containers/Rcs/index.scss +85 -6
- package/app/v2Containers/Rcs/messages.js +10 -1
- package/app/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
- package/app/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +41136 -1566
- package/app/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
- package/app/v2Containers/Rcs/tests/index.test.js +41 -38
- package/app/v2Containers/Rcs/tests/mockData.js +38 -0
- package/app/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
- package/app/v2Containers/Rcs/tests/utils.test.js +379 -1
- package/app/v2Containers/Rcs/utils.js +358 -10
- package/app/v2Containers/Sms/Create/index.js +122 -39
- package/app/v2Containers/Sms/Create/messages.js +4 -0
- package/app/v2Containers/Sms/Edit/index.js +37 -3
- package/app/v2Containers/Sms/commonMethods.js +3 -6
- package/app/v2Containers/Sms/smsFormDataHelpers.js +67 -0
- package/app/v2Containers/Sms/tests/commonMethods.test.js +122 -0
- package/app/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/app/v2Containers/SmsTrai/Create/index.js +9 -4
- package/app/v2Containers/SmsTrai/Create/index.scss +1 -1
- package/app/v2Containers/SmsTrai/Edit/constants.js +2 -0
- package/app/v2Containers/SmsTrai/Edit/index.js +667 -160
- package/app/v2Containers/SmsTrai/Edit/index.scss +121 -0
- package/app/v2Containers/SmsTrai/Edit/messages.js +9 -4
- package/app/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4590 -2436
- package/app/v2Containers/SmsWrapper/index.js +41 -8
- package/app/v2Containers/TagList/index.js +63 -2
- package/app/v2Containers/TagList/messages.js +8 -0
- package/app/v2Containers/TagList/tests/TagList.test.js +122 -20
- package/app/v2Containers/TagList/tests/mockdata.js +17 -0
- package/app/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/app/v2Containers/Templates/_templates.scss +61 -2
- package/app/v2Containers/Templates/actions.js +11 -0
- package/app/v2Containers/Templates/constants.js +2 -0
- package/app/v2Containers/Templates/index.js +90 -40
- package/app/v2Containers/Templates/reducer.js +3 -1
- package/app/v2Containers/Templates/sagas.js +57 -12
- package/app/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/app/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
- package/app/v2Containers/Templates/tests/reducer.test.js +12 -0
- package/app/v2Containers/Templates/tests/sagas.test.js +193 -12
- package/app/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
- package/app/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
- package/app/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
- package/app/v2Containers/TemplatesV2/index.js +147 -49
- package/app/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/app/v2Containers/Viber/index.js +9 -10
- package/app/v2Containers/Viber/index.scss +1 -1
- package/app/v2Containers/WebPush/Create/components/BrandIconSection.test.js +264 -0
- package/app/v2Containers/WebPush/Create/components/MessageSection.js +78 -19
- package/app/v2Containers/WebPush/Create/components/MessageSection.test.js +82 -0
- package/app/v2Containers/WebPush/Create/components/__snapshots__/BrandIconSection.test.js.snap +187 -0
- package/app/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +25 -17
- package/app/v2Containers/WebPush/Create/hooks/useAiraTriggerPosition.js +80 -0
- package/app/v2Containers/WebPush/Create/hooks/useAiraTriggerPosition.test.js +210 -0
- package/app/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -5
- package/app/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
- package/app/v2Containers/WebPush/Create/index.js +36 -6
- package/app/v2Containers/WebPush/Create/index.scss +5 -0
- package/app/v2Containers/WebPush/Create/messages.js +8 -1
- package/app/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +269 -0
- package/app/v2Containers/WebPush/Create/utils/validation.js +31 -15
- package/app/v2Containers/WebPush/Create/utils/validation.test.js +72 -24
- package/app/v2Containers/Whatsapp/index.js +28 -53
- package/app/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +26939 -3982
- package/app/v2Containers/Whatsapp/tests/index.test.js +172 -0
- package/app/v2Containers/Zalo/index.js +5 -11
- package/package.json +2 -2
- package/version +9 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import PropTypes from 'prop-types';
|
|
10
10
|
import React, {
|
|
11
|
-
useState, useEffect, useMemo, useRef,
|
|
11
|
+
useState, useEffect, useMemo, useRef, useCallback,
|
|
12
12
|
} from 'react';
|
|
13
13
|
import { FormattedMessage } from 'react-intl';
|
|
14
14
|
import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
|
|
@@ -28,7 +28,8 @@ import CustomValuesEditor from './CustomValuesEditor';
|
|
|
28
28
|
import SendTestMessage from './SendTestMessage';
|
|
29
29
|
import PreviewSection from './PreviewSection';
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
import * as Api from '../../services/api';
|
|
32
|
+
import { extractTemplateVariables } from '../../utils/templateVarUtils';
|
|
32
33
|
import {
|
|
33
34
|
CHANNELS,
|
|
34
35
|
TEST,
|
|
@@ -65,10 +66,125 @@ import {
|
|
|
65
66
|
IN_APP_CHANNEL_NAME,
|
|
66
67
|
MOBILE_PUSH_CHANNEL_NAME,
|
|
67
68
|
CHANNEL,
|
|
69
|
+
PHONE_NUMBER,
|
|
70
|
+
DYNAMIC_URL,
|
|
71
|
+
IMAGE,
|
|
72
|
+
VIDEO,
|
|
73
|
+
URL,
|
|
74
|
+
PREVIEW_TAB_RCS,
|
|
75
|
+
PREVIEW_TAB_SMS_FALLBACK,
|
|
76
|
+
CHANNELS_USING_ANDROID_PREVIEW_DEVICE,
|
|
77
|
+
RCS_TEST_META_CONTENT_TYPE_RICHCARD,
|
|
78
|
+
RCS_TEST_META_CARD_TYPE_STANDALONE,
|
|
79
|
+
RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
|
|
80
|
+
RCS_TEST_META_CARD_WIDTH_SMALL,
|
|
81
|
+
SMS_MUSTACHE_TAG_PATTERN,
|
|
68
82
|
} from './constants';
|
|
69
|
-
|
|
70
|
-
// Import utilities
|
|
71
83
|
import { getCdnUrl } from '../../utils/cdnTransformation';
|
|
84
|
+
import {
|
|
85
|
+
normalizePreviewApiPayload,
|
|
86
|
+
extractPreviewFromLiquidResponse,
|
|
87
|
+
getSmsFallbackTextForTagExtraction,
|
|
88
|
+
} from './previewApiUtils';
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Drop empty GSM rows. RCS/DLT responses often set gsm_sender_id equal to domainName — keep those rows
|
|
92
|
+
* for RCS defaults (ModifyDeliverySettings uses the same rule for the sender dropdown).
|
|
93
|
+
*/
|
|
94
|
+
const filterUsableGsmSendersForDomain = (domain, gsmSenders, { skipDomainNameEchoFilter = false } = {}) => {
|
|
95
|
+
const normalizedDomainName =
|
|
96
|
+
domain?.domainName != null ? String(domain.domainName).trim().toLowerCase() : '';
|
|
97
|
+
return (gsmSenders || []).filter((gsmSenderRow) => {
|
|
98
|
+
const rawValue = gsmSenderRow?.value;
|
|
99
|
+
if (rawValue == null) return false;
|
|
100
|
+
const trimmedSenderValue = String(rawValue).trim();
|
|
101
|
+
if (!trimmedSenderValue) return false;
|
|
102
|
+
const senderMatchesDomainLabel =
|
|
103
|
+
normalizedDomainName && trimmedSenderValue.toLowerCase() === normalizedDomainName;
|
|
104
|
+
if (!skipDomainNameEchoFilter && senderMatchesDomainLabel) return false;
|
|
105
|
+
return true;
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/** Preview payload from Redux may be an Immutable Map — normalize for React state. */
|
|
110
|
+
const toPlainPreviewData = (data) => {
|
|
111
|
+
if (data == null) return null;
|
|
112
|
+
const plain = typeof data.toJS === 'function' ? data.toJS() : data;
|
|
113
|
+
return normalizePreviewApiPayload(plain);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Merge existing customValues with tag keys from categorized groups.
|
|
118
|
+
* Each group is { required, optional } (arrays of tag objects with fullPath).
|
|
119
|
+
* Preserves existing values; adds '' for any tag key not yet present.
|
|
120
|
+
* Reusable for RCS+fallback and any flow that needs to ensure customValues has keys for tags.
|
|
121
|
+
*/
|
|
122
|
+
const mergeCustomValuesWithTagKeys = (prev, ...categorizedGroups) => {
|
|
123
|
+
const next = { ...(prev || {}) };
|
|
124
|
+
categorizedGroups.forEach((group) => {
|
|
125
|
+
[...(group.required || []), ...(group.optional || [])].forEach((tag) => {
|
|
126
|
+
const key = tag?.fullPath;
|
|
127
|
+
if (key && next[key] === undefined) next[key] = '';
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
return next;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/** True when `body` contains `{{name}}` mustache tokens (user-fillable personalization tags).
|
|
134
|
+
* DLT `{#name#}` slots are pre-bound template variables and are intentionally excluded. */
|
|
135
|
+
const smsTemplateHasMustacheTags = (body) =>
|
|
136
|
+
typeof body === 'string' && SMS_MUSTACHE_TAG_PATTERN.test(body);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Build tag rows from `{{…}}` mustache tokens only — DLT `{#…#}` slots are excluded because
|
|
140
|
+
* they are pre-bound template variables, not user-fillable personalization tags.
|
|
141
|
+
* Passing a mustache-only captureRegex to extractTemplateVariables skips the DLT branch.
|
|
142
|
+
* A non-global regex is used so ensureGlobalRegexForExecLoop creates a fresh instance on each call.
|
|
143
|
+
*/
|
|
144
|
+
const buildSyntheticSmsMustacheTags = (body = '') => {
|
|
145
|
+
if (!body || typeof body !== 'string') return [];
|
|
146
|
+
return extractTemplateVariables(body, /\{\{([^}]+)\}\}/).map((name) => ({
|
|
147
|
+
name,
|
|
148
|
+
metaData: { userDriven: false },
|
|
149
|
+
children: [],
|
|
150
|
+
}));
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** RCS createMessageMeta: media shape (mediaUrl, thumbnailUrl, height string). */
|
|
154
|
+
const normalizeRcsTestCardMedia = (media) => {
|
|
155
|
+
if (!media || typeof media !== 'object') return undefined;
|
|
156
|
+
const mediaUrl =
|
|
157
|
+
media.mediaUrl != null && String(media.mediaUrl).trim() !== ''
|
|
158
|
+
? String(media.mediaUrl)
|
|
159
|
+
: media.url != null && String(media.url).trim() !== ''
|
|
160
|
+
? String(media.url)
|
|
161
|
+
: '';
|
|
162
|
+
const thumbnailUrl = media.thumbnailUrl != null ? String(media.thumbnailUrl) : '';
|
|
163
|
+
const height = media.height != null ? String(media.height) : undefined;
|
|
164
|
+
const out = { mediaUrl, thumbnailUrl };
|
|
165
|
+
if (height) out.height = height;
|
|
166
|
+
return out;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/** RCS createMessageMeta: suggestion shape (index, type, text, phoneNumber, url, postback). */
|
|
170
|
+
const mapRcsSuggestionForTestMeta = (suggestionRow, index) => ({
|
|
171
|
+
index,
|
|
172
|
+
type: suggestionRow?.type ?? '',
|
|
173
|
+
text: suggestionRow?.text != null ? String(suggestionRow.text) : '',
|
|
174
|
+
phoneNumber:
|
|
175
|
+
suggestionRow?.phoneNumber != null
|
|
176
|
+
? String(suggestionRow.phoneNumber)
|
|
177
|
+
: suggestionRow?.phone_number != null
|
|
178
|
+
? String(suggestionRow.phone_number)
|
|
179
|
+
: '',
|
|
180
|
+
url: suggestionRow?.url !== undefined ? suggestionRow.url : null,
|
|
181
|
+
postback:
|
|
182
|
+
suggestionRow?.postback != null
|
|
183
|
+
? String(suggestionRow.postback)
|
|
184
|
+
: suggestionRow?.text != null
|
|
185
|
+
? String(suggestionRow.text)
|
|
186
|
+
: '',
|
|
187
|
+
});
|
|
72
188
|
|
|
73
189
|
/**
|
|
74
190
|
* Preview Component Factory - REMOVED IN PHASE 5
|
|
@@ -80,7 +196,7 @@ import { getCdnUrl } from '../../utils/cdnTransformation';
|
|
|
80
196
|
*/
|
|
81
197
|
const CommonTestAndPreview = (props) => {
|
|
82
198
|
const {
|
|
83
|
-
intl: { formatMessage },
|
|
199
|
+
intl: { formatMessage, locale: userLocale = 'en' },
|
|
84
200
|
show,
|
|
85
201
|
onClose,
|
|
86
202
|
channel, // The channel: 'EMAIL', 'SMS', 'RCS', etc.
|
|
@@ -105,6 +221,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
105
221
|
updatePreviewErrors,
|
|
106
222
|
fetchPrefilledValuesError,
|
|
107
223
|
fetchPrefilledValuesErrors,
|
|
224
|
+
senderDetailsByChannel = {},
|
|
225
|
+
wecrmAccounts = [],
|
|
226
|
+
isLoadingSenderDetails = false,
|
|
227
|
+
orgUnitId = -1,
|
|
108
228
|
// Email-specific props
|
|
109
229
|
beeInstance,
|
|
110
230
|
currentTab = 1,
|
|
@@ -112,19 +232,29 @@ const CommonTestAndPreview = (props) => {
|
|
|
112
232
|
...additionalProps
|
|
113
233
|
} = props;
|
|
114
234
|
|
|
235
|
+
const smsFallbackContent = additionalProps?.smsFallbackContent;
|
|
236
|
+
const smsFallbackTextForTagExtraction = useMemo(
|
|
237
|
+
() => getSmsFallbackTextForTagExtraction(smsFallbackContent),
|
|
238
|
+
[smsFallbackContent],
|
|
239
|
+
);
|
|
115
240
|
// ============================================
|
|
116
241
|
// STATE MANAGEMENT
|
|
117
242
|
// ============================================
|
|
118
243
|
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
|
119
244
|
const [requiredTags, setRequiredTags] = useState([]);
|
|
120
245
|
const [optionalTags, setOptionalTags] = useState([]);
|
|
246
|
+
const [smsFallbackExtractedTags, setSmsFallbackExtractedTags] = useState([]);
|
|
247
|
+
const [smsFallbackRequiredTags, setSmsFallbackRequiredTags] = useState([]);
|
|
248
|
+
const [smsFallbackOptionalTags, setSmsFallbackOptionalTags] = useState([]);
|
|
249
|
+
const [isExtractingSmsFallbackTags, setIsExtractingSmsFallbackTags] = useState(false);
|
|
121
250
|
const [customValues, setCustomValues] = useState({});
|
|
122
251
|
const [showJSON, setShowJSON] = useState(false);
|
|
123
252
|
const [tagsExtracted, setTagsExtracted] = useState(false);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const initialDevice = (channel === CHANNELS.SMS || channel === CHANNELS.WHATSAPP || channel === CHANNELS.RCS || channel === CHANNELS.INAPP || channel === CHANNELS.MOBILEPUSH || channel === CHANNELS.VIBER) ? ANDROID : DESKTOP;
|
|
253
|
+
|
|
254
|
+
const initialDevice = CHANNELS_USING_ANDROID_PREVIEW_DEVICE.includes(channel) ? ANDROID : DESKTOP;
|
|
127
255
|
const [previewDevice, setPreviewDevice] = useState(initialDevice);
|
|
256
|
+
const [activePreviewTab, setActivePreviewTab] = useState(PREVIEW_TAB_RCS);
|
|
257
|
+
const [smsFallbackPreviewText, setSmsFallbackPreviewText] = useState(undefined);
|
|
128
258
|
// Track if a preview call has been made (to know when to use previewDataHtml vs raw content)
|
|
129
259
|
const [hasPreviewCallBeenMade, setHasPreviewCallBeenMade] = useState(false);
|
|
130
260
|
const [previewDataHtml, setPreviewDataHtml] = useState(() => {
|
|
@@ -146,15 +276,205 @@ const CommonTestAndPreview = (props) => {
|
|
|
146
276
|
const [selectedTestEntities, setSelectedTestEntities] = useState([]);
|
|
147
277
|
const [beeContent, setBeeContent] = useState(''); // Track BEE editor content separately (EMAIL only)
|
|
148
278
|
const previousBeeContentRef = useRef(''); // Track previous BEE content (EMAIL only)
|
|
279
|
+
// Delivery settings for Test and Preview (SMS, Email, WhatsApp) — user selection only
|
|
280
|
+
const [testPreviewDeliverySettings, setTestPreviewDeliverySettings] = useState({
|
|
281
|
+
[CHANNELS.SMS]: {
|
|
282
|
+
domainId: null, domainGatewayMapId: null, gsmSenderId: '', cdmaSenderId: '',
|
|
283
|
+
},
|
|
284
|
+
[CHANNELS.EMAIL]: {
|
|
285
|
+
domainId: null, domainGatewayMapId: null, senderEmail: '', senderLabel: '', senderReplyTo: '',
|
|
286
|
+
},
|
|
287
|
+
[CHANNELS.WHATSAPP]: {
|
|
288
|
+
domainId: null, senderMobNum: '', sourceAccountIdentifier: '',
|
|
289
|
+
},
|
|
290
|
+
[CHANNELS.RCS]: {
|
|
291
|
+
domainId: null,
|
|
292
|
+
domainGatewayMapId: null,
|
|
293
|
+
gsmSenderId: '',
|
|
294
|
+
smsFallbackDomainId: null,
|
|
295
|
+
cdmaSenderId: '', // gsmSenderId = RCS sender (domainId|senderId), cdmaSenderId = SMS fallback
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS];
|
|
300
|
+
const formDataForSendTest = formData ?? (content && typeof content === 'object' && !Array.isArray(content) ? content : formData);
|
|
301
|
+
const smsTemplateConfigs = formDataForSendTest?.templateConfigs || {};
|
|
302
|
+
const smsTraiDltEnabled = !!smsTemplateConfigs?.traiDltEnabled;
|
|
303
|
+
const registeredSenderIds = smsTemplateConfigs?.registeredSenderIds || [];
|
|
304
|
+
|
|
305
|
+
// Fetch sender details and WeCRM accounts when Test & Preview opens (SMS, Email, WhatsApp, RCS — same process)
|
|
306
|
+
useEffect(() => {
|
|
307
|
+
if (!show || !channel) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (channelsWithDeliverySettings.includes(channel)) {
|
|
311
|
+
if (actions.getSenderDetailsRequested) {
|
|
312
|
+
actions.getSenderDetailsRequested({ channel, orgUnitId: orgUnitId ?? -1 });
|
|
313
|
+
}
|
|
314
|
+
// SMS domains/senders are needed for RCS delivery UI (fallback row + slidebox) whenever RCS is open — not only when fallback body exists.
|
|
315
|
+
if (channel === CHANNELS.RCS && actions.getSenderDetailsRequested) {
|
|
316
|
+
actions.getSenderDetailsRequested({ channel: CHANNELS.SMS, orgUnitId: orgUnitId ?? -1 });
|
|
317
|
+
}
|
|
318
|
+
if (channel === CHANNELS.WHATSAPP && actions.getWeCrmAccountsRequested) {
|
|
319
|
+
actions.getWeCrmAccountsRequested({ sourceName: CHANNELS.WHATSAPP });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}, [show, channel, orgUnitId, actions]);
|
|
323
|
+
|
|
324
|
+
const findDefault = (arr) => (arr && arr.find((x) => x.default)) || (arr && arr[0]) || {};
|
|
325
|
+
|
|
326
|
+
// Auto-set default delivery setting when sender details load (campaigns-style: first domain + default/first sender)
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
if (!channel || !channelsWithDeliverySettings.includes(channel)) return;
|
|
329
|
+
|
|
330
|
+
if (channel === CHANNELS.RCS) {
|
|
331
|
+
const rcsDomainRows = senderDetailsByChannel?.[CHANNELS.RCS] || [];
|
|
332
|
+
const smsFallbackDomainRows = senderDetailsByChannel?.[CHANNELS.SMS] || [];
|
|
333
|
+
if (!rcsDomainRows.length) return;
|
|
334
|
+
|
|
335
|
+
const currentRcsDeliverySettings = testPreviewDeliverySettings?.[CHANNELS.RCS] || {};
|
|
336
|
+
const isRcsGsmSenderUnset = !currentRcsDeliverySettings?.gsmSenderId;
|
|
337
|
+
const isSmsFallbackSenderUnset = !currentRcsDeliverySettings?.cdmaSenderId;
|
|
338
|
+
const isRcsDeliveryFullyUnset = isRcsGsmSenderUnset && isSmsFallbackSenderUnset;
|
|
339
|
+
const shouldOnlyFillSmsFallbackSender =
|
|
340
|
+
!isRcsGsmSenderUnset && isSmsFallbackSenderUnset && smsFallbackDomainRows.length > 0;
|
|
341
|
+
|
|
342
|
+
if (!isRcsDeliveryFullyUnset && !shouldOnlyFillSmsFallbackSender) return;
|
|
343
|
+
|
|
344
|
+
const firstRcsDomain = rcsDomainRows[0];
|
|
345
|
+
const firstSmsFallbackDomain = smsFallbackDomainRows[0];
|
|
346
|
+
const usableRcsGsmSenders = filterUsableGsmSendersForDomain(
|
|
347
|
+
firstRcsDomain,
|
|
348
|
+
firstRcsDomain?.gsmSenders,
|
|
349
|
+
{ skipDomainNameEchoFilter: true },
|
|
350
|
+
);
|
|
351
|
+
const usableSmsFallbackGsmSenders = firstSmsFallbackDomain
|
|
352
|
+
? filterUsableGsmSendersForDomain(firstSmsFallbackDomain, firstSmsFallbackDomain?.gsmSenders)
|
|
353
|
+
: [];
|
|
354
|
+
const defaultRcsGsmSender = usableRcsGsmSenders[0];
|
|
355
|
+
const defaultSmsFallbackGsmSender = usableSmsFallbackGsmSenders[0];
|
|
356
|
+
const rcsSenderCompositeValue =
|
|
357
|
+
firstRcsDomain?.domainId != null && defaultRcsGsmSender?.value != null
|
|
358
|
+
? `${firstRcsDomain.domainId}|${defaultRcsGsmSender.value}`
|
|
359
|
+
: (defaultRcsGsmSender?.value || '');
|
|
360
|
+
const smsFallbackSenderCompositeValue =
|
|
361
|
+
firstSmsFallbackDomain?.domainId != null && defaultSmsFallbackGsmSender?.value != null
|
|
362
|
+
? `${firstSmsFallbackDomain.domainId}|${defaultSmsFallbackGsmSender.value}`
|
|
363
|
+
: (defaultSmsFallbackGsmSender?.value || '');
|
|
364
|
+
|
|
365
|
+
setTestPreviewDeliverySettings((prev) => {
|
|
366
|
+
const previousRcsSettings = prev?.[CHANNELS.RCS] || {};
|
|
367
|
+
if (shouldOnlyFillSmsFallbackSender) {
|
|
368
|
+
if (!smsFallbackSenderCompositeValue) return prev;
|
|
369
|
+
return {
|
|
370
|
+
...prev,
|
|
371
|
+
[CHANNELS.RCS]: {
|
|
372
|
+
...previousRcsSettings,
|
|
373
|
+
smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
|
|
374
|
+
cdmaSenderId: smsFallbackSenderCompositeValue,
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
...prev,
|
|
380
|
+
[CHANNELS.RCS]: {
|
|
381
|
+
domainId: firstRcsDomain?.domainId ?? null,
|
|
382
|
+
domainGatewayMapId: firstRcsDomain?.dgmId ?? null,
|
|
383
|
+
gsmSenderId: rcsSenderCompositeValue,
|
|
384
|
+
smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
|
|
385
|
+
cdmaSenderId: smsFallbackSenderCompositeValue,
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
});
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const domains = senderDetailsByChannel[channel];
|
|
393
|
+
if (!domains || domains.length === 0) return;
|
|
394
|
+
const {
|
|
395
|
+
domainId = '', gsmSenderId = '', cdmaSenderId = '', senderEmail = '', senderMobNum = '',
|
|
396
|
+
} = testPreviewDeliverySettings[channel] || {};
|
|
397
|
+
const isEmptySelection = !domainId && !gsmSenderId && !cdmaSenderId && !senderEmail && !senderMobNum;
|
|
398
|
+
if (!isEmptySelection) return;
|
|
399
|
+
|
|
400
|
+
const whatsappAccountFromForm = channel === CHANNELS.WHATSAPP ? formData?.accountName : undefined;
|
|
401
|
+
const matchedWhatsappAccount = whatsappAccountFromForm
|
|
402
|
+
? (wecrmAccounts || []).find((account) => account?.name === whatsappAccountFromForm)
|
|
403
|
+
: null;
|
|
404
|
+
const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled && registeredSenderIds?.length
|
|
405
|
+
? domains.filter((domain) => (domain?.gsmSenders || []).some((gsm) =>
|
|
406
|
+
registeredSenderIds?.includes(gsm?.value)))
|
|
407
|
+
: domains;
|
|
408
|
+
const [defaultDomain] = domains;
|
|
409
|
+
const [firstSmsDomain] = smsDomains;
|
|
410
|
+
let firstDomain = defaultDomain;
|
|
411
|
+
if (channel === CHANNELS.WHATSAPP && matchedWhatsappAccount?.sourceAccountIdentifier) {
|
|
412
|
+
firstDomain = domains.find((domain) => domain?.sourceAccountIdentifier === matchedWhatsappAccount.sourceAccountIdentifier) || defaultDomain;
|
|
413
|
+
} else if (channel === CHANNELS.SMS) {
|
|
414
|
+
firstDomain = firstSmsDomain;
|
|
415
|
+
}
|
|
416
|
+
if (!firstDomain) return;
|
|
417
|
+
setTestPreviewDeliverySettings((prev) => {
|
|
418
|
+
const next = { ...prev };
|
|
419
|
+
if (channel === CHANNELS.SMS) {
|
|
420
|
+
const smsGsmSenders = smsTraiDltEnabled
|
|
421
|
+
? (firstDomain?.gsmSenders || []).filter((gsm) => registeredSenderIds?.includes(gsm?.value))
|
|
422
|
+
: firstDomain?.gsmSenders;
|
|
423
|
+
next[channel] = {
|
|
424
|
+
domainId: firstDomain.domainId,
|
|
425
|
+
domainGatewayMapId: firstDomain.dgmId,
|
|
426
|
+
gsmSenderId: findDefault(smsGsmSenders)?.value || smsGsmSenders?.[0]?.value || '',
|
|
427
|
+
cdmaSenderId: findDefault(firstDomain.cdmaSenders)?.value || '',
|
|
428
|
+
};
|
|
429
|
+
} else if (channel === CHANNELS.EMAIL) {
|
|
430
|
+
const defSender = findDefault(firstDomain.emailSenders);
|
|
431
|
+
const defReply = findDefault(firstDomain.emailRepliers);
|
|
432
|
+
next[channel] = {
|
|
433
|
+
domainId: firstDomain.domainId,
|
|
434
|
+
domainGatewayMapId: firstDomain.dgmId,
|
|
435
|
+
senderEmail: defSender?.value || '',
|
|
436
|
+
senderLabel: defSender?.label || '',
|
|
437
|
+
senderReplyTo: defReply?.value || '',
|
|
438
|
+
};
|
|
439
|
+
} else if (channel === CHANNELS.WHATSAPP) {
|
|
440
|
+
const accId = firstDomain.sourceAccountIdentifier;
|
|
441
|
+
next[channel] = {
|
|
442
|
+
domainId: firstDomain.domainId,
|
|
443
|
+
senderMobNum: firstDomain.gsmSenders?.[0]?.value || '',
|
|
444
|
+
sourceAccountIdentifier: matchedWhatsappAccount?.sourceAccountIdentifier || accId || '',
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
return next;
|
|
448
|
+
});
|
|
449
|
+
}, [channel, formData?.accountName, senderDetailsByChannel, smsTraiDltEnabled, registeredSenderIds, wecrmAccounts]);
|
|
149
450
|
|
|
150
451
|
// ============================================
|
|
151
452
|
// MEMOIZED VALUES
|
|
152
453
|
// ============================================
|
|
153
454
|
|
|
455
|
+
const allTags = useMemo(
|
|
456
|
+
() => [...requiredTags, ...optionalTags, ...smsFallbackRequiredTags, ...smsFallbackOptionalTags],
|
|
457
|
+
[requiredTags, optionalTags, smsFallbackRequiredTags, smsFallbackOptionalTags]
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
const allRequiredTags = useMemo(
|
|
461
|
+
() => [...requiredTags, ...smsFallbackRequiredTags],
|
|
462
|
+
[requiredTags, smsFallbackRequiredTags]
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
const buildEmptyValues = useCallback(
|
|
466
|
+
() => allTags.reduce((acc, tag) => {
|
|
467
|
+
const key = tag?.fullPath;
|
|
468
|
+
if (key) acc[key] = '';
|
|
469
|
+
return acc;
|
|
470
|
+
}, {}),
|
|
471
|
+
[allTags]
|
|
472
|
+
);
|
|
473
|
+
|
|
154
474
|
// Check if update preview button should be disabled
|
|
155
475
|
const isUpdatePreviewDisabled = useMemo(() => (
|
|
156
|
-
|
|
157
|
-
), [
|
|
476
|
+
allRequiredTags.some((tag) => !customValues[tag.fullPath])
|
|
477
|
+
), [allRequiredTags, customValues]);
|
|
158
478
|
|
|
159
479
|
// Get current content based on channel and editor type
|
|
160
480
|
const getCurrentContent = useMemo(() => {
|
|
@@ -198,6 +518,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
198
518
|
return currentTabData.base['sms-editor'];
|
|
199
519
|
}
|
|
200
520
|
}
|
|
521
|
+
// DLT / Test & Preview shape: { templateConfigs: { template, templateId, ... } }
|
|
522
|
+
if (formData.templateConfigs?.template) {
|
|
523
|
+
const smsDltTemplateValue = formData.templateConfigs.template;
|
|
524
|
+
if (typeof smsDltTemplateValue === 'string') return smsDltTemplateValue;
|
|
525
|
+
if (Array.isArray(smsDltTemplateValue)) return smsDltTemplateValue.join('');
|
|
526
|
+
return '';
|
|
527
|
+
}
|
|
201
528
|
}
|
|
202
529
|
|
|
203
530
|
// SMS channel fallback - if formData is not available, use content directly
|
|
@@ -243,7 +570,70 @@ const CommonTestAndPreview = (props) => {
|
|
|
243
570
|
return content || '';
|
|
244
571
|
}, [channel, formData, currentTab, beeContent, content, beeInstance]);
|
|
245
572
|
|
|
246
|
-
|
|
573
|
+
const leftPanelExtractedTags = useMemo(() => {
|
|
574
|
+
if (channel === CHANNELS.SMS) {
|
|
575
|
+
const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
|
|
576
|
+
if (!smsTemplateHasMustacheTags(smsEditorBody)) return [];
|
|
577
|
+
const extractTagsFromApi = extractedTags ?? [];
|
|
578
|
+
if (extractTagsFromApi.length > 0) return extractTagsFromApi;
|
|
579
|
+
return buildSyntheticSmsMustacheTags(smsEditorBody);
|
|
580
|
+
}
|
|
581
|
+
const hasFallbackSmsBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
582
|
+
if (channel === CHANNELS.RCS && hasFallbackSmsBody) {
|
|
583
|
+
const rcsPrimaryTags = extractedTags ?? [];
|
|
584
|
+
const fallbackSmsTextForTags = smsFallbackTextForTagExtraction ?? '';
|
|
585
|
+
const fallbackSmsTagRows = smsTemplateHasMustacheTags(fallbackSmsTextForTags)
|
|
586
|
+
? (smsFallbackExtractedTags?.length > 0
|
|
587
|
+
? smsFallbackExtractedTags
|
|
588
|
+
: buildSyntheticSmsMustacheTags(fallbackSmsTextForTags))
|
|
589
|
+
: [];
|
|
590
|
+
const mergedRcsAndFallbackTags = [...rcsPrimaryTags, ...fallbackSmsTagRows];
|
|
591
|
+
if (mergedRcsAndFallbackTags.length > 0) return mergedRcsAndFallbackTags;
|
|
592
|
+
return buildSyntheticSmsMustacheTags(fallbackSmsTextForTags);
|
|
593
|
+
}
|
|
594
|
+
return extractedTags ?? [];
|
|
595
|
+
}, [
|
|
596
|
+
channel,
|
|
597
|
+
extractedTags,
|
|
598
|
+
getCurrentContent,
|
|
599
|
+
smsFallbackContent,
|
|
600
|
+
smsFallbackExtractedTags,
|
|
601
|
+
smsFallbackTextForTagExtraction,
|
|
602
|
+
]);
|
|
603
|
+
|
|
604
|
+
const isRcsSmsFallbackPreviewEnabled =
|
|
605
|
+
channel === CHANNELS.RCS
|
|
606
|
+
&& !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
607
|
+
// Only treat as SMS when user is on the Fallback SMS tab — not whenever fallback exists (RCS tab needs RCS preview API).
|
|
608
|
+
const isSmsFallbackTabActive = isRcsSmsFallbackPreviewEnabled && activePreviewTab === PREVIEW_TAB_SMS_FALLBACK;
|
|
609
|
+
const activeChannelForActions = isSmsFallbackTabActive ? CHANNELS.SMS : channel;
|
|
610
|
+
// VarSegment slot values live in rcsSmsFallbackVarMapped; raw templateContent alone is stale for /preview Body.
|
|
611
|
+
const resolvedSmsFallbackBodyForPreviewTab =
|
|
612
|
+
smsFallbackTextForTagExtraction
|
|
613
|
+
|| smsFallbackContent?.templateContent
|
|
614
|
+
|| smsFallbackContent?.content
|
|
615
|
+
|| '';
|
|
616
|
+
const activeContentForActions = isSmsFallbackTabActive
|
|
617
|
+
? resolvedSmsFallbackBodyForPreviewTab
|
|
618
|
+
: getCurrentContent;
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* SMS fallback pane must show /preview API result when user updated preview on that tab (plain text).
|
|
622
|
+
* Skip when resolvedBody is RCS-shaped (e.g. user last previewed on RCS tab).
|
|
623
|
+
*/
|
|
624
|
+
// smsFallbackPreviewText is the single source of truth for the resolved SMS fallback preview.
|
|
625
|
+
// It is set only by syncSmsFallbackPreview (called from handleUpdatePreview and the
|
|
626
|
+
// prefilled-values effect) and reset to undefined on discard / slidebox close.
|
|
627
|
+
// Using previewDataHtml as a fallback is unsafe because that state is shared with the primary
|
|
628
|
+
// RCS preview and can contain stale SMS or RCS content.
|
|
629
|
+
const smsFallbackResolvedText = useMemo(() => {
|
|
630
|
+
const hasFallbackBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
631
|
+
if (channel !== CHANNELS.RCS || !hasFallbackBody) return undefined;
|
|
632
|
+
if (smsFallbackPreviewText != null) return smsFallbackPreviewText;
|
|
633
|
+
return undefined;
|
|
634
|
+
}, [channel, smsFallbackContent, smsFallbackPreviewText]);
|
|
635
|
+
|
|
636
|
+
// Build test entities tree data from testCustomers prop
|
|
247
637
|
const testEntitiesTreeData = useMemo(() => {
|
|
248
638
|
const groupsNode = {
|
|
249
639
|
title: 'Groups',
|
|
@@ -355,6 +745,37 @@ const CommonTestAndPreview = (props) => {
|
|
|
355
745
|
}
|
|
356
746
|
};
|
|
357
747
|
|
|
748
|
+
/**
|
|
749
|
+
* When RCS has SMS fallback, refresh fallback preview text via the same Liquid /preview API
|
|
750
|
+
* (separate call with SMS channel + fallback template body). Used after primary preview updates.
|
|
751
|
+
*/
|
|
752
|
+
const syncSmsFallbackPreview = async (customValuesForResolve, selectedCustomerObj) => {
|
|
753
|
+
const fallbackBodyForLiquidPreview =
|
|
754
|
+
getSmsFallbackTextForTagExtraction(smsFallbackContent)
|
|
755
|
+
|| smsFallbackContent?.templateContent
|
|
756
|
+
|| smsFallbackContent?.content
|
|
757
|
+
|| '';
|
|
758
|
+
if (channel !== CHANNELS.RCS || !String(fallbackBodyForLiquidPreview).trim()) return;
|
|
759
|
+
try {
|
|
760
|
+
const smsFallbackPayload = preparePreviewPayload(
|
|
761
|
+
CHANNELS.SMS,
|
|
762
|
+
formData || {},
|
|
763
|
+
fallbackBodyForLiquidPreview,
|
|
764
|
+
customValuesForResolve,
|
|
765
|
+
selectedCustomerObj
|
|
766
|
+
);
|
|
767
|
+
const fallbackResponse = await Api.updateEmailPreview(smsFallbackPayload);
|
|
768
|
+
const fallbackPreview = extractPreviewFromLiquidResponse(fallbackResponse);
|
|
769
|
+
setSmsFallbackPreviewText(
|
|
770
|
+
typeof fallbackPreview?.resolvedBody === 'string'
|
|
771
|
+
? fallbackPreview.resolvedBody
|
|
772
|
+
: undefined
|
|
773
|
+
);
|
|
774
|
+
} catch (e) {
|
|
775
|
+
/* keep existing smsFallbackPreviewText on failure */
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
|
|
358
779
|
/**
|
|
359
780
|
* Prepare payload for tag extraction based on channel
|
|
360
781
|
*/
|
|
@@ -442,7 +863,175 @@ const CommonTestAndPreview = (props) => {
|
|
|
442
863
|
* Prepare payload for test message sending based on channel
|
|
443
864
|
* Updated to match API structure with ouId, sourceEntityId, module, deliverySettings, etc.
|
|
444
865
|
*/
|
|
445
|
-
|
|
866
|
+
|
|
867
|
+
const getCarouselMappedData = (carouselData = []) => carouselData.map((carousel) => {
|
|
868
|
+
const {
|
|
869
|
+
bodyText, imageUrl, videoUrl, videoPreviewImg, buttons, mediaType, cardVarMapped, bodyTemplate,
|
|
870
|
+
} = carousel || {};
|
|
871
|
+
const buttonData = buttons.map((button, index) => {
|
|
872
|
+
const {
|
|
873
|
+
type, text, phone_number: phoneNumber, urlType, url,
|
|
874
|
+
} = button || {};
|
|
875
|
+
const buttonObj = {
|
|
876
|
+
type,
|
|
877
|
+
text,
|
|
878
|
+
index,
|
|
879
|
+
};
|
|
880
|
+
if (type === PHONE_NUMBER) {
|
|
881
|
+
buttonObj.phoneNumber = phoneNumber;
|
|
882
|
+
}
|
|
883
|
+
if (type === URL) {
|
|
884
|
+
buttonObj.url = url;
|
|
885
|
+
if (urlType === DYNAMIC_URL) {
|
|
886
|
+
const dynamicUrlPayload = url?.match(/{{(.*?)}}/g);
|
|
887
|
+
buttonObj.dynamicUrlPayload = dynamicUrlPayload?.length === 1 ? dynamicUrlPayload[0] : '';
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return buttonObj;
|
|
891
|
+
});
|
|
892
|
+
return {
|
|
893
|
+
body: bodyText,
|
|
894
|
+
cardVarMapped,
|
|
895
|
+
bodyTemplate,
|
|
896
|
+
media: {
|
|
897
|
+
...(mediaType?.toLowerCase() === IMAGE.toLowerCase() && {
|
|
898
|
+
url: imageUrl,
|
|
899
|
+
}),
|
|
900
|
+
...(mediaType?.toLowerCase() === VIDEO.toLowerCase() && {
|
|
901
|
+
url: videoUrl,
|
|
902
|
+
previewUrl: videoPreviewImg,
|
|
903
|
+
}),
|
|
904
|
+
},
|
|
905
|
+
buttons: buttonData,
|
|
906
|
+
mediaType: mediaType?.toUpperCase(),
|
|
907
|
+
};
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Build createMessageMeta payload for RCS (test message).
|
|
912
|
+
* rcsMessageContent: { channel, accountId?, rcsRichCardContent: { contentType, cardType, cardSettings, cardContent }, smsFallBackContent? }
|
|
913
|
+
* Then rcsDeliverySettings, executionParams, clientName last.
|
|
914
|
+
*/
|
|
915
|
+
const buildRcsTestMessagePayload = (formDataObj, _contentStr, customValuesObj, deliverySettingsOverride, basePayload, rcsExtra = {}) => {
|
|
916
|
+
const plainCustom =
|
|
917
|
+
customValuesObj != null && typeof customValuesObj.toJS === 'function'
|
|
918
|
+
? customValuesObj.toJS()
|
|
919
|
+
: (customValuesObj || {});
|
|
920
|
+
const userVarMap = Object.fromEntries(
|
|
921
|
+
Object.entries(plainCustom).filter(([, v]) => v != null && String(v).trim() !== '')
|
|
922
|
+
);
|
|
923
|
+
const rcsData = formDataObj?.versions?.base?.content?.RCS ?? formDataObj?.content?.RCS ?? {};
|
|
924
|
+
const rcsContent = rcsData?.rcsContent || {};
|
|
925
|
+
const smsFallback = rcsData?.smsFallBackContent || {};
|
|
926
|
+
let cardContentList = [];
|
|
927
|
+
if (Array.isArray(rcsContent?.cardContent)) {
|
|
928
|
+
cardContentList = rcsContent.cardContent;
|
|
929
|
+
} else if (rcsContent?.cardContent) {
|
|
930
|
+
cardContentList = [rcsContent.cardContent];
|
|
931
|
+
}
|
|
932
|
+
// Merge test customValues into cardVarMapped (template snapshot + user-entered RCS + fallback SMS tags).
|
|
933
|
+
const cardContent = cardContentList.map((rcsCard) => {
|
|
934
|
+
const baseMap = rcsCard.cardVarMapped || {};
|
|
935
|
+
const mergedCardVarMapped =
|
|
936
|
+
Object.keys(userVarMap).length > 0 ? { ...baseMap, ...userVarMap } : baseMap;
|
|
937
|
+
const mediaNorm = rcsCard?.media ? normalizeRcsTestCardMedia(rcsCard.media) : undefined;
|
|
938
|
+
const suggestionsRaw = Array.isArray(rcsCard?.suggestions) ? rcsCard.suggestions : [];
|
|
939
|
+
const suggestionsMapped = suggestionsRaw.map((suggestionItem, i) =>
|
|
940
|
+
mapRcsSuggestionForTestMeta(suggestionItem, i));
|
|
941
|
+
return {
|
|
942
|
+
title: rcsCard?.title ?? '',
|
|
943
|
+
description: rcsCard?.description ?? '',
|
|
944
|
+
mediaType: rcsCard?.mediaType ?? MEDIA_TYPE_TEXT,
|
|
945
|
+
...(mediaNorm && { media: mediaNorm }),
|
|
946
|
+
...(Object.keys(mergedCardVarMapped).length > 0 && { cardVarMapped: mergedCardVarMapped }),
|
|
947
|
+
...(suggestionsMapped.length > 0 && { suggestions: suggestionsMapped }),
|
|
948
|
+
};
|
|
949
|
+
});
|
|
950
|
+
// Prefer parent `smsFallbackContent` snapshot (rcsExtra) — includes VarSegment-resolved body via
|
|
951
|
+
// getSmsFallbackTextForTagExtraction. Nested formData.smsFallBackContent is often stale vs live editor.
|
|
952
|
+
const rcsExtraFallbackTemplate = rcsExtra?.smsFallbackTemplateContent;
|
|
953
|
+
const hasResolvedFallbackBodyFromRcsExtra =
|
|
954
|
+
rcsExtraFallbackTemplate != null
|
|
955
|
+
&& String(rcsExtraFallbackTemplate).trim() !== '';
|
|
956
|
+
const smsMessageRaw = hasResolvedFallbackBodyFromRcsExtra
|
|
957
|
+
? String(rcsExtraFallbackTemplate)
|
|
958
|
+
: (smsFallback?.smsContent ?? smsFallback?.message ?? '');
|
|
959
|
+
const smsSenderFromDelivery = deliverySettingsOverride?.cdmaSenderId?.includes('|')
|
|
960
|
+
? deliverySettingsOverride.cdmaSenderId.split('|')[1]
|
|
961
|
+
: deliverySettingsOverride?.cdmaSenderId;
|
|
962
|
+
const deliveryFallbackSmsId =
|
|
963
|
+
typeof smsSenderFromDelivery === 'string' ? smsSenderFromDelivery.trim() : '';
|
|
964
|
+
const creativeFallbackSmsId =
|
|
965
|
+
smsFallback?.senderId != null ? String(smsFallback.senderId).trim() : '';
|
|
966
|
+
const fallbackSmsSenderIdForChannel = deliveryFallbackSmsId || creativeFallbackSmsId || '';
|
|
967
|
+
|
|
968
|
+
const smsFallBackContent =
|
|
969
|
+
smsMessageRaw.trim() !== ''
|
|
970
|
+
? { message: smsMessageRaw }
|
|
971
|
+
: undefined;
|
|
972
|
+
|
|
973
|
+
// accountId: WeCRM account id (not sourceAccountIdentifier) for createMessageMeta
|
|
974
|
+
const accountIdForMeta =
|
|
975
|
+
rcsContent?.accountId != null && String(rcsContent.accountId).trim() !== ''
|
|
976
|
+
? String(rcsContent.accountId)
|
|
977
|
+
: undefined;
|
|
978
|
+
|
|
979
|
+
const rcsRichCardContent = {
|
|
980
|
+
contentType: RCS_TEST_META_CONTENT_TYPE_RICHCARD,
|
|
981
|
+
cardType: rcsContent?.cardType ?? RCS_TEST_META_CARD_TYPE_STANDALONE,
|
|
982
|
+
cardSettings: rcsContent?.cardSettings ?? {
|
|
983
|
+
cardOrientation: RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
|
|
984
|
+
cardWidth: RCS_TEST_META_CARD_WIDTH_SMALL,
|
|
985
|
+
},
|
|
986
|
+
...(cardContent.length > 0 && { cardContent }),
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
const rcsMessageContent = {
|
|
990
|
+
channel: CHANNELS.RCS,
|
|
991
|
+
...(accountIdForMeta && { accountId: accountIdForMeta }),
|
|
992
|
+
rcsRichCardContent,
|
|
993
|
+
...(smsFallBackContent && { smsFallBackContent }),
|
|
994
|
+
};
|
|
995
|
+
const rcsComposite = deliverySettingsOverride?.gsmSenderId ?? '';
|
|
996
|
+
const [rcsDomainId, rcsSenderId] = rcsComposite.includes('|') ? rcsComposite.split('|') : ['', rcsComposite];
|
|
997
|
+
const rcsDeliverySettings = {
|
|
998
|
+
channelSettings: {
|
|
999
|
+
channel: CHANNELS.RCS,
|
|
1000
|
+
rcsSender: (rcsSenderId || deliverySettingsOverride?.rcsSender) ?? '',
|
|
1001
|
+
domainId:
|
|
1002
|
+
rcsDomainId !== '' && rcsDomainId !== undefined && !Number.isNaN(Number(rcsDomainId))
|
|
1003
|
+
? Number(rcsDomainId)
|
|
1004
|
+
: (deliverySettingsOverride?.domainId ?? 0),
|
|
1005
|
+
fallbackSmsSenderId: fallbackSmsSenderIdForChannel,
|
|
1006
|
+
},
|
|
1007
|
+
additionalSettings: {
|
|
1008
|
+
useTinyUrl: false,
|
|
1009
|
+
encryptUrl: false,
|
|
1010
|
+
linkTrackingEnabled: false,
|
|
1011
|
+
bypassControlUser: false,
|
|
1012
|
+
userSubscriptionDisabled: false,
|
|
1013
|
+
},
|
|
1014
|
+
};
|
|
1015
|
+
const { clientName: baseClientName = CLIENT_NAME_CREATIVES, ...restBase } = basePayload;
|
|
1016
|
+
return {
|
|
1017
|
+
...restBase,
|
|
1018
|
+
rcsMessageContent,
|
|
1019
|
+
rcsDeliverySettings,
|
|
1020
|
+
executionParams: {},
|
|
1021
|
+
clientName: baseClientName,
|
|
1022
|
+
};
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
const prepareTestMessagePayload = (
|
|
1026
|
+
channelType,
|
|
1027
|
+
formDataObj,
|
|
1028
|
+
contentStr,
|
|
1029
|
+
customValuesObj,
|
|
1030
|
+
recipientDetails,
|
|
1031
|
+
previewDataObj,
|
|
1032
|
+
deliverySettingsOverride,
|
|
1033
|
+
rcsExtra = {},
|
|
1034
|
+
) => {
|
|
446
1035
|
// Base payload structure common to all channels
|
|
447
1036
|
const basePayload = {
|
|
448
1037
|
ouId: -1,
|
|
@@ -468,6 +1057,14 @@ const CommonTestAndPreview = (props) => {
|
|
|
468
1057
|
? resolveTagsInText(subject, customValuesObj)
|
|
469
1058
|
: subject;
|
|
470
1059
|
|
|
1060
|
+
const emailChannelSettings = {
|
|
1061
|
+
channel: CHANNELS.EMAIL,
|
|
1062
|
+
senderLabel: deliverySettingsOverride?.senderLabel ?? '',
|
|
1063
|
+
senderId: deliverySettingsOverride?.senderEmail ?? '',
|
|
1064
|
+
replyToId: deliverySettingsOverride?.senderReplyTo ?? '',
|
|
1065
|
+
domainGatewayMapId: deliverySettingsOverride?.domainGatewayMapId ?? '',
|
|
1066
|
+
domainId: deliverySettingsOverride?.domainId ?? '',
|
|
1067
|
+
};
|
|
471
1068
|
return {
|
|
472
1069
|
...basePayload,
|
|
473
1070
|
emailDeliverySettings: {
|
|
@@ -478,12 +1075,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
478
1075
|
skipRateLimit: false,
|
|
479
1076
|
bypassControlUser: false,
|
|
480
1077
|
},
|
|
481
|
-
channelSettings:
|
|
482
|
-
channel: CHANNELS.EMAIL,
|
|
483
|
-
senderLabel: '',
|
|
484
|
-
senderId: '',
|
|
485
|
-
replyToId: '',
|
|
486
|
-
},
|
|
1078
|
+
channelSettings: emailChannelSettings,
|
|
487
1079
|
},
|
|
488
1080
|
emailMessageContent: {
|
|
489
1081
|
channel: CHANNELS.EMAIL,
|
|
@@ -520,8 +1112,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
520
1112
|
},
|
|
521
1113
|
smsDeliverySettings: {
|
|
522
1114
|
channelSettings: {
|
|
523
|
-
|
|
524
|
-
|
|
1115
|
+
channel: CHANNELS.SMS,
|
|
1116
|
+
gsmSenderId: deliverySettingsOverride?.gsmSenderId ?? '',
|
|
1117
|
+
domainId: deliverySettingsOverride?.domainId ?? null,
|
|
1118
|
+
domainGatewayMapId: deliverySettingsOverride?.domainGatewayMapId ?? '',
|
|
1119
|
+
targetNdnc: false,
|
|
1120
|
+
cdmaSenderId: deliverySettingsOverride?.cdmaSenderId ?? '',
|
|
525
1121
|
},
|
|
526
1122
|
additionalSettings: {
|
|
527
1123
|
useTinyUrl: false,
|
|
@@ -653,20 +1249,21 @@ const CommonTestAndPreview = (props) => {
|
|
|
653
1249
|
|
|
654
1250
|
// Add carousel data if mediaType is CAROUSEL
|
|
655
1251
|
if (mediaType === MEDIA_TYPE_CAROUSEL && formDataObj?.carouselData) {
|
|
656
|
-
templateConfigs.cards = formDataObj.carouselData;
|
|
1252
|
+
templateConfigs.cards = getCarouselMappedData(formDataObj.carouselData);
|
|
657
1253
|
templateConfigs.mediaType = formDataObj?.carouselMediaType?.toUpperCase() || MEDIA_TYPE_IMAGE;
|
|
658
1254
|
}
|
|
659
1255
|
|
|
660
|
-
// Extract delivery settings
|
|
661
|
-
const senderMobNum = formDataObj?.senderMobNum
|
|
662
|
-
const domainId = formDataObj?.domainId
|
|
1256
|
+
// Extract delivery settings (override from Test & Preview delivery settings when present)
|
|
1257
|
+
const senderMobNum = deliverySettingsOverride?.senderMobNum ?? formDataObj?.senderMobNum ?? additionalProps?.senderMobNum ?? '';
|
|
1258
|
+
const domainId = deliverySettingsOverride?.domainId ?? formDataObj?.domainId ?? additionalProps?.domainId ?? null;
|
|
1259
|
+
const sourceAccountIdentifier = deliverySettingsOverride?.sourceAccountIdentifier ?? formDataObj?.sourceAccountIdentifier ?? '';
|
|
663
1260
|
|
|
664
1261
|
return {
|
|
665
1262
|
...basePayload,
|
|
666
1263
|
whatsappMessageContent: {
|
|
667
|
-
messageBody: templateEditorValue || '',
|
|
1264
|
+
messageBody: resolvedMessageBody || templateEditorValue || '',
|
|
668
1265
|
accountId: formDataObj?.accountId || '',
|
|
669
|
-
sourceAccountIdentifier: formDataObj?.sourceAccountIdentifier || '',
|
|
1266
|
+
sourceAccountIdentifier: sourceAccountIdentifier || formDataObj?.sourceAccountIdentifier || '',
|
|
670
1267
|
accountName: formDataObj?.accountName || '',
|
|
671
1268
|
templateConfigs,
|
|
672
1269
|
channel: CHANNELS.WHATSAPP,
|
|
@@ -689,16 +1286,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
689
1286
|
}
|
|
690
1287
|
|
|
691
1288
|
case CHANNELS.RCS:
|
|
692
|
-
return
|
|
693
|
-
...basePayload,
|
|
694
|
-
rcsMessageContent: {
|
|
695
|
-
channel: CHANNELS.RCS,
|
|
696
|
-
messageBody: contentStr,
|
|
697
|
-
rcsType: additionalProps?.rcsType,
|
|
698
|
-
rcsImageSrc: formDataObj?.rcsImageSrc,
|
|
699
|
-
rcsSuggestions: formDataObj?.rcsSuggestions,
|
|
700
|
-
},
|
|
701
|
-
};
|
|
1289
|
+
return buildRcsTestMessagePayload(formDataObj, contentStr, customValuesObj, deliverySettingsOverride, basePayload, rcsExtra);
|
|
702
1290
|
|
|
703
1291
|
case CHANNELS.INAPP: {
|
|
704
1292
|
// InApp payload structure similar to MobilePush
|
|
@@ -1800,6 +2388,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
1800
2388
|
formatMessage,
|
|
1801
2389
|
lastModified: formData?.lastModified,
|
|
1802
2390
|
updatedByName: formData?.updatedByName,
|
|
2391
|
+
smsFallbackContent: isRcsSmsFallbackPreviewEnabled ? smsFallbackContent : null,
|
|
2392
|
+
smsFallbackResolvedText,
|
|
2393
|
+
activePreviewTab,
|
|
2394
|
+
onPreviewTabChange: setActivePreviewTab,
|
|
1803
2395
|
};
|
|
1804
2396
|
};
|
|
1805
2397
|
|
|
@@ -1839,7 +2431,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
1839
2431
|
}, [show, beeInstance, currentTab, channel]);
|
|
1840
2432
|
|
|
1841
2433
|
/**
|
|
1842
|
-
* Initial data load when slidebox opens
|
|
2434
|
+
* Initial data load when slidebox opens.
|
|
2435
|
+
* EXTRACT TAGS CALL SITES (on open/edit RCS):
|
|
2436
|
+
* 1. Here (non-email): actions.extractTagsRequested() at line ~2161 when show is true.
|
|
2437
|
+
* 2. RCS SMS fallback useEffect below: Api.extractTagsWithMetaData() for fallback message.
|
|
2438
|
+
* 3. handleExtractTags() (user clicks "Enter custom values for tags") – not from effects.
|
|
2439
|
+
* The "Process extracted tags" effect only processes API results and must not call extract again.
|
|
1843
2440
|
*/
|
|
1844
2441
|
useEffect(() => {
|
|
1845
2442
|
if (show) {
|
|
@@ -1924,7 +2521,61 @@ const CommonTestAndPreview = (props) => {
|
|
|
1924
2521
|
actions.getTestGroupsRequested();
|
|
1925
2522
|
}
|
|
1926
2523
|
}
|
|
1927
|
-
|
|
2524
|
+
// getCurrentContent: RCS applies cardVarMapped → placeholder resolution; re-extract when it changes.
|
|
2525
|
+
}, [show, beeInstance, currentTab, channel, getCurrentContent]);
|
|
2526
|
+
|
|
2527
|
+
/**
|
|
2528
|
+
* RCS with SMS fallback: extract tags for fallback SMS content as well
|
|
2529
|
+
* (so we can show a separate "Fallback SMS tags" section in left panel).
|
|
2530
|
+
*/
|
|
2531
|
+
useEffect(() => {
|
|
2532
|
+
let cancelled = false;
|
|
2533
|
+
|
|
2534
|
+
if (!show || channel !== CHANNELS.RCS) {
|
|
2535
|
+
return () => {
|
|
2536
|
+
cancelled = true;
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
if (!smsFallbackContent?.templateContent && !smsFallbackContent?.content) {
|
|
2541
|
+
setSmsFallbackExtractedTags([]);
|
|
2542
|
+
setSmsFallbackRequiredTags([]);
|
|
2543
|
+
setSmsFallbackOptionalTags([]);
|
|
2544
|
+
return () => {
|
|
2545
|
+
cancelled = true;
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
setIsExtractingSmsFallbackTags(true);
|
|
2550
|
+
(async () => {
|
|
2551
|
+
try {
|
|
2552
|
+
const fallbackBodyForExtractApi = getSmsFallbackTextForTagExtraction(smsFallbackContent);
|
|
2553
|
+
const payload = {
|
|
2554
|
+
messageTitle: '',
|
|
2555
|
+
messageBody: fallbackBodyForExtractApi || '',
|
|
2556
|
+
};
|
|
2557
|
+
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
|
|
2558
|
+
let smsFallbackTagTree = response?.data ?? [];
|
|
2559
|
+
if (!Array.isArray(smsFallbackTagTree)) smsFallbackTagTree = [];
|
|
2560
|
+
if (!smsTemplateHasMustacheTags(fallbackBodyForExtractApi)) {
|
|
2561
|
+
smsFallbackTagTree = [];
|
|
2562
|
+
} else if (smsFallbackTagTree.length === 0) {
|
|
2563
|
+
smsFallbackTagTree = buildSyntheticSmsMustacheTags(fallbackBodyForExtractApi);
|
|
2564
|
+
}
|
|
2565
|
+
if (cancelled) return;
|
|
2566
|
+
setSmsFallbackExtractedTags(smsFallbackTagTree);
|
|
2567
|
+
} catch (e) {
|
|
2568
|
+
if (cancelled) return;
|
|
2569
|
+
setSmsFallbackExtractedTags([]);
|
|
2570
|
+
} finally {
|
|
2571
|
+
if (!cancelled) setIsExtractingSmsFallbackTags(false);
|
|
2572
|
+
}
|
|
2573
|
+
})();
|
|
2574
|
+
|
|
2575
|
+
return () => {
|
|
2576
|
+
cancelled = true;
|
|
2577
|
+
};
|
|
2578
|
+
}, [show, channel, smsFallbackContent]);
|
|
1928
2579
|
|
|
1929
2580
|
/**
|
|
1930
2581
|
* Email-specific: Handle content updates for both BEE and CKEditor
|
|
@@ -1976,15 +2627,22 @@ const CommonTestAndPreview = (props) => {
|
|
|
1976
2627
|
setSelectedCustomer(null);
|
|
1977
2628
|
setRequiredTags([]);
|
|
1978
2629
|
setOptionalTags([]);
|
|
2630
|
+
setSmsFallbackExtractedTags([]);
|
|
2631
|
+
setSmsFallbackRequiredTags([]);
|
|
2632
|
+
setSmsFallbackOptionalTags([]);
|
|
2633
|
+
setIsExtractingSmsFallbackTags(false);
|
|
1979
2634
|
setCustomValues({});
|
|
1980
2635
|
setShowJSON(false);
|
|
1981
2636
|
setTagsExtracted(false);
|
|
1982
2637
|
setPreviewDevice(DESKTOP);
|
|
2638
|
+
setActivePreviewTab(PREVIEW_TAB_RCS);
|
|
2639
|
+
setSmsFallbackPreviewText(undefined);
|
|
1983
2640
|
setSelectedTestEntities([]);
|
|
1984
2641
|
actions.clearPrefilledValues();
|
|
1985
2642
|
} else {
|
|
1986
2643
|
// Reset device to initialDevice when opening (Android for mobile channels, Desktop for others)
|
|
1987
2644
|
setPreviewDevice(initialDevice);
|
|
2645
|
+
setActivePreviewTab(PREVIEW_TAB_RCS);
|
|
1988
2646
|
}
|
|
1989
2647
|
}, [show, initialDevice]);
|
|
1990
2648
|
|
|
@@ -1993,79 +2651,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
1993
2651
|
*/
|
|
1994
2652
|
useEffect(() => {
|
|
1995
2653
|
if (previewData) {
|
|
1996
|
-
setPreviewDataHtml(previewData);
|
|
2654
|
+
setPreviewDataHtml(toPlainPreviewData(previewData));
|
|
1997
2655
|
}
|
|
1998
2656
|
}, [previewData]);
|
|
1999
2657
|
|
|
2000
|
-
/**
|
|
2001
|
-
* Process extracted tags and categorize them
|
|
2002
|
-
*/
|
|
2003
|
-
useEffect(() => {
|
|
2004
|
-
// Categorize tags into required and optional
|
|
2005
|
-
const required = [];
|
|
2006
|
-
const optional = [];
|
|
2007
|
-
let hasPersonalizationTags = false;
|
|
2008
|
-
|
|
2009
|
-
if (extractedTags?.length > 0) {
|
|
2010
|
-
const processTag = (tag, parentPath = '') => {
|
|
2011
|
-
const currentPath = parentPath ? `${parentPath}.${tag.name}` : tag.name;
|
|
2012
|
-
|
|
2013
|
-
// Skip unsubscribe tag for input fields
|
|
2014
|
-
if (tag?.name === UNSUBSCRIBE_TAG_NAME) {
|
|
2015
|
-
return;
|
|
2016
|
-
}
|
|
2017
|
-
|
|
2018
|
-
hasPersonalizationTags = true;
|
|
2019
|
-
|
|
2020
|
-
if (tag?.metaData?.userDriven === false) {
|
|
2021
|
-
required.push({
|
|
2022
|
-
...tag,
|
|
2023
|
-
fullPath: currentPath,
|
|
2024
|
-
});
|
|
2025
|
-
} else if (tag?.metaData?.userDriven === true) {
|
|
2026
|
-
optional.push({
|
|
2027
|
-
...tag,
|
|
2028
|
-
fullPath: currentPath,
|
|
2029
|
-
});
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
if (tag?.children?.length > 0) {
|
|
2033
|
-
tag.children.forEach((child) => processTag(child, currentPath));
|
|
2034
|
-
}
|
|
2035
|
-
};
|
|
2036
|
-
|
|
2037
|
-
extractedTags.forEach((tag) => processTag(tag));
|
|
2038
|
-
|
|
2039
|
-
if (hasPersonalizationTags) {
|
|
2040
|
-
setRequiredTags(required);
|
|
2041
|
-
setOptionalTags(optional);
|
|
2042
|
-
setTagsExtracted(true); // Mark tags as extracted and processed
|
|
2043
|
-
|
|
2044
|
-
// Initialize custom values for required tags
|
|
2045
|
-
const initialValues = {};
|
|
2046
|
-
required.forEach((tag) => {
|
|
2047
|
-
initialValues[tag?.fullPath] = '';
|
|
2048
|
-
});
|
|
2049
|
-
optional.forEach((tag) => {
|
|
2050
|
-
initialValues[tag?.fullPath] = '';
|
|
2051
|
-
});
|
|
2052
|
-
setCustomValues(initialValues);
|
|
2053
|
-
} else {
|
|
2054
|
-
// Reset all tag-related state if no personalization tags
|
|
2055
|
-
setRequiredTags([]);
|
|
2056
|
-
setOptionalTags([]);
|
|
2057
|
-
setCustomValues({});
|
|
2058
|
-
setTagsExtracted(false);
|
|
2059
|
-
}
|
|
2060
|
-
} else {
|
|
2061
|
-
// Reset all tag-related state if no tags
|
|
2062
|
-
setRequiredTags([]);
|
|
2063
|
-
setOptionalTags([]);
|
|
2064
|
-
setCustomValues({});
|
|
2065
|
-
setTagsExtracted(false);
|
|
2066
|
-
}
|
|
2067
|
-
}, [extractedTags]);
|
|
2068
|
-
|
|
2069
2658
|
/**
|
|
2070
2659
|
* Handle customer selection and fetch prefilled values
|
|
2071
2660
|
*/
|
|
@@ -2073,17 +2662,15 @@ const CommonTestAndPreview = (props) => {
|
|
|
2073
2662
|
if (selectedCustomer && config.enableCustomerSearch !== false) {
|
|
2074
2663
|
setTagsExtracted(true); // Auto-open custom values editor
|
|
2075
2664
|
|
|
2076
|
-
// Get all available tags
|
|
2077
|
-
const allTags = [...requiredTags, ...optionalTags];
|
|
2078
2665
|
const requiredTagObj = {};
|
|
2079
|
-
|
|
2666
|
+
allRequiredTags.forEach((tag) => {
|
|
2080
2667
|
requiredTagObj[tag?.fullPath] = '';
|
|
2081
2668
|
});
|
|
2082
2669
|
if (allTags.length > 0) {
|
|
2083
2670
|
const payload = preparePreviewPayload(
|
|
2084
|
-
|
|
2671
|
+
activeChannelForActions,
|
|
2085
2672
|
formData || {},
|
|
2086
|
-
|
|
2673
|
+
activeContentForActions,
|
|
2087
2674
|
{
|
|
2088
2675
|
...requiredTagObj,
|
|
2089
2676
|
},
|
|
@@ -2092,7 +2679,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2092
2679
|
actions.getPrefilledValuesRequested(payload);
|
|
2093
2680
|
}
|
|
2094
2681
|
}
|
|
2095
|
-
}, [selectedCustomer]);
|
|
2682
|
+
}, [selectedCustomer, allTags.length, activeChannelForActions, activePreviewTab]);
|
|
2096
2683
|
|
|
2097
2684
|
/**
|
|
2098
2685
|
* Update custom values with prefilled values from API
|
|
@@ -2103,7 +2690,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2103
2690
|
if (prefilledValues && selectedCustomer) {
|
|
2104
2691
|
// Always replace all values with prefilled values
|
|
2105
2692
|
const updatedValues = {};
|
|
2106
|
-
|
|
2693
|
+
allTags.forEach((tag) => {
|
|
2107
2694
|
updatedValues[tag?.fullPath] = prefilledValues[tag?.fullPath] || '';
|
|
2108
2695
|
});
|
|
2109
2696
|
|
|
@@ -2111,16 +2698,17 @@ const CommonTestAndPreview = (props) => {
|
|
|
2111
2698
|
|
|
2112
2699
|
// Update preview with prefilled values (this is a valid preview call trigger)
|
|
2113
2700
|
const payload = preparePreviewPayload(
|
|
2114
|
-
|
|
2701
|
+
activeChannelForActions,
|
|
2115
2702
|
formData || {},
|
|
2116
|
-
|
|
2703
|
+
activeContentForActions,
|
|
2117
2704
|
updatedValues,
|
|
2118
2705
|
selectedCustomer
|
|
2119
2706
|
);
|
|
2120
2707
|
actions.updatePreviewRequested(payload);
|
|
2121
2708
|
setHasPreviewCallBeenMade(true); // Mark that preview call was made
|
|
2709
|
+
void syncSmsFallbackPreview(updatedValues, selectedCustomer);
|
|
2122
2710
|
}
|
|
2123
|
-
}, [JSON.stringify(prefilledValues), selectedCustomer]);
|
|
2711
|
+
}, [JSON.stringify(prefilledValues), selectedCustomer, activeChannelForActions, activePreviewTab]);
|
|
2124
2712
|
|
|
2125
2713
|
/**
|
|
2126
2714
|
* Map channel constants to display names (lowercase for message)
|
|
@@ -2213,11 +2801,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2213
2801
|
setTagsExtracted(true); // Auto-open custom values editor
|
|
2214
2802
|
|
|
2215
2803
|
// Clear any existing values while waiting for prefilled values
|
|
2216
|
-
|
|
2217
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2218
|
-
emptyValues[tag?.fullPath] = '';
|
|
2219
|
-
});
|
|
2220
|
-
setCustomValues(emptyValues);
|
|
2804
|
+
setCustomValues(buildEmptyValues());
|
|
2221
2805
|
};
|
|
2222
2806
|
|
|
2223
2807
|
/**
|
|
@@ -2231,11 +2815,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2231
2815
|
actions.clearPreviewErrors();
|
|
2232
2816
|
|
|
2233
2817
|
// Initialize empty values for all tags
|
|
2234
|
-
|
|
2235
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2236
|
-
emptyValues[tag?.fullPath] = '';
|
|
2237
|
-
});
|
|
2238
|
-
setCustomValues(emptyValues);
|
|
2818
|
+
setCustomValues(buildEmptyValues());
|
|
2239
2819
|
|
|
2240
2820
|
// Don't make preview call when clearing selection - just reset to raw content
|
|
2241
2821
|
// Preview will be shown using raw formData/content
|
|
@@ -2271,17 +2851,17 @@ const CommonTestAndPreview = (props) => {
|
|
|
2271
2851
|
*/
|
|
2272
2852
|
const handleDiscardCustomValues = () => {
|
|
2273
2853
|
// Initialize empty values for all tags
|
|
2274
|
-
const emptyValues =
|
|
2275
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2276
|
-
emptyValues[tag?.fullPath] = '';
|
|
2277
|
-
});
|
|
2854
|
+
const emptyValues = buildEmptyValues();
|
|
2278
2855
|
setCustomValues(emptyValues);
|
|
2279
2856
|
|
|
2857
|
+
// Reset SMS fallback preview so it shows raw template (with {{tags}} visible) after discard
|
|
2858
|
+
setSmsFallbackPreviewText(undefined);
|
|
2859
|
+
|
|
2280
2860
|
// Update preview with empty values (this is a valid preview call trigger)
|
|
2281
2861
|
const payload = preparePreviewPayload(
|
|
2282
|
-
|
|
2862
|
+
activeChannelForActions,
|
|
2283
2863
|
formData || {},
|
|
2284
|
-
|
|
2864
|
+
activeContentForActions,
|
|
2285
2865
|
emptyValues,
|
|
2286
2866
|
selectedCustomer
|
|
2287
2867
|
);
|
|
@@ -2296,13 +2876,15 @@ const CommonTestAndPreview = (props) => {
|
|
|
2296
2876
|
const handleUpdatePreview = async () => {
|
|
2297
2877
|
try {
|
|
2298
2878
|
const payload = preparePreviewPayload(
|
|
2299
|
-
|
|
2879
|
+
activeChannelForActions,
|
|
2300
2880
|
formData || {},
|
|
2301
|
-
|
|
2881
|
+
activeContentForActions,
|
|
2302
2882
|
customValues,
|
|
2303
2883
|
selectedCustomer
|
|
2304
2884
|
);
|
|
2305
2885
|
await actions.updatePreviewRequested(payload);
|
|
2886
|
+
|
|
2887
|
+
await syncSmsFallbackPreview(customValues, selectedCustomer);
|
|
2306
2888
|
setHasPreviewCallBeenMade(true); // Mark that preview call was made
|
|
2307
2889
|
} catch (error) {
|
|
2308
2890
|
CapNotification.error({
|
|
@@ -2312,25 +2894,115 @@ const CommonTestAndPreview = (props) => {
|
|
|
2312
2894
|
};
|
|
2313
2895
|
|
|
2314
2896
|
/**
|
|
2315
|
-
*
|
|
2897
|
+
* Categorize extracted tags into required/optional.
|
|
2316
2898
|
*/
|
|
2317
|
-
const
|
|
2318
|
-
|
|
2319
|
-
|
|
2899
|
+
const categorizeTags = (tagsTree = []) => {
|
|
2900
|
+
const required = [];
|
|
2901
|
+
const optional = [];
|
|
2902
|
+
let hasPersonalizationTags = false;
|
|
2903
|
+
const processTag = (tag, parentPath = '') => {
|
|
2904
|
+
const currentPath = parentPath ? `${parentPath}.${tag.name}` : tag.name;
|
|
2905
|
+
|
|
2906
|
+
// Skip unsubscribe tag for input fields
|
|
2907
|
+
if (tag?.name === UNSUBSCRIBE_TAG_NAME) return;
|
|
2908
|
+
|
|
2909
|
+
hasPersonalizationTags = true;
|
|
2910
|
+
const userDriven = tag?.metaData?.userDriven;
|
|
2911
|
+
if (userDriven === true) {
|
|
2912
|
+
optional.push({ ...tag, fullPath: currentPath });
|
|
2913
|
+
} else {
|
|
2914
|
+
// false or missing (SMS/DLT extract often omits metaData) → required for test values
|
|
2915
|
+
required.push({ ...tag, fullPath: currentPath });
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
if (tag?.children?.length > 0) {
|
|
2919
|
+
tag.children.forEach((child) => processTag(child, currentPath));
|
|
2920
|
+
}
|
|
2921
|
+
};
|
|
2922
|
+
|
|
2923
|
+
(tagsTree || []).forEach((tag) => processTag(tag));
|
|
2924
|
+
return { required, optional, hasPersonalizationTags };
|
|
2925
|
+
};
|
|
2926
|
+
|
|
2927
|
+
/**
|
|
2928
|
+
* Apply tag extraction when content comes from RCS + SMS fallback (no API call).
|
|
2929
|
+
*/
|
|
2930
|
+
const applyRcsSmsFallbackTagExtraction = () => {
|
|
2931
|
+
const rcsPrimaryCategorized = categorizeTags(extractedTags ?? []);
|
|
2932
|
+
const fallbackSmsResolvedForTags = smsFallbackTextForTagExtraction ?? '';
|
|
2933
|
+
let fallbackSmsTagTree = smsFallbackExtractedTags?.length > 0
|
|
2934
|
+
? smsFallbackExtractedTags
|
|
2935
|
+
: buildSyntheticSmsMustacheTags(fallbackSmsResolvedForTags);
|
|
2936
|
+
if (!smsTemplateHasMustacheTags(fallbackSmsResolvedForTags)) {
|
|
2937
|
+
fallbackSmsTagTree = [];
|
|
2938
|
+
}
|
|
2939
|
+
const fallbackSmsCategorized = categorizeTags(fallbackSmsTagTree);
|
|
2940
|
+
setRequiredTags(rcsPrimaryCategorized.required);
|
|
2941
|
+
setOptionalTags(rcsPrimaryCategorized.optional);
|
|
2942
|
+
setSmsFallbackRequiredTags(fallbackSmsCategorized.required);
|
|
2943
|
+
setSmsFallbackOptionalTags(fallbackSmsCategorized.optional);
|
|
2944
|
+
setTagsExtracted(
|
|
2945
|
+
rcsPrimaryCategorized.hasPersonalizationTags || fallbackSmsCategorized.hasPersonalizationTags,
|
|
2946
|
+
);
|
|
2947
|
+
setCustomValues((prev) => mergeCustomValuesWithTagKeys(prev, rcsPrimaryCategorized, fallbackSmsCategorized));
|
|
2948
|
+
};
|
|
2949
|
+
|
|
2950
|
+
/**
|
|
2951
|
+
* When extract-tags API returns, map Redux `extractedTags` into required/optional so
|
|
2952
|
+
* CustomValuesEditor shows personalization fields (effect was previously commented out).
|
|
2953
|
+
* RCS + SMS fallback: merge primary + fallback tag trees when fallback template exists.
|
|
2954
|
+
*/
|
|
2955
|
+
useEffect(() => {
|
|
2956
|
+
if (!show) return;
|
|
2957
|
+
const hasFallbackSmsTemplate = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
2958
|
+
if (channel === CHANNELS.RCS && hasFallbackSmsTemplate) {
|
|
2959
|
+
applyRcsSmsFallbackTagExtraction();
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
|
|
2963
|
+
let smsTagSource = channel === CHANNELS.SMS && (!extractedTags || extractedTags.length === 0)
|
|
2964
|
+
? buildSyntheticSmsMustacheTags(smsEditorBody)
|
|
2965
|
+
: (extractedTags ?? []);
|
|
2966
|
+
if (channel === CHANNELS.SMS && !smsTemplateHasMustacheTags(smsEditorBody)) {
|
|
2967
|
+
smsTagSource = [];
|
|
2968
|
+
}
|
|
2969
|
+
const { required, optional, hasPersonalizationTags } = categorizeTags(smsTagSource);
|
|
2970
|
+
setRequiredTags(required);
|
|
2971
|
+
setOptionalTags(optional);
|
|
2972
|
+
setTagsExtracted(hasPersonalizationTags);
|
|
2973
|
+
setCustomValues((prev) => mergeCustomValuesWithTagKeys(prev, { required, optional }, { required: [], optional: [] }));
|
|
2974
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- applyRcsSmsFallbackTagExtraction closes over latest extractedTags/smsFallbackExtractedTags
|
|
2975
|
+
}, [show, extractedTags, channel, smsFallbackContent, smsFallbackExtractedTags, getCurrentContent, smsFallbackTextForTagExtraction]);
|
|
2320
2976
|
|
|
2977
|
+
/**
|
|
2978
|
+
* Get content to run tag extraction on (channel-specific).
|
|
2979
|
+
*/
|
|
2980
|
+
const getContentForTagExtraction = () => {
|
|
2981
|
+
let contentToExtract = activeContentForActions;
|
|
2321
2982
|
if (channel === CHANNELS.EMAIL && formData) {
|
|
2322
2983
|
const currentTabData = formData[currentTab - 1];
|
|
2323
2984
|
const activeTab = currentTabData?.activeTab;
|
|
2324
2985
|
const templateContent = currentTabData?.[activeTab]?.['template-content'];
|
|
2325
2986
|
contentToExtract = templateContent || contentToExtract;
|
|
2326
2987
|
}
|
|
2988
|
+
return contentToExtract;
|
|
2989
|
+
};
|
|
2327
2990
|
|
|
2328
|
-
|
|
2991
|
+
/**
|
|
2992
|
+
* Handle extract tags
|
|
2993
|
+
*/
|
|
2994
|
+
const handleExtractTags = () => {
|
|
2995
|
+
if (channel === CHANNELS.RCS) {
|
|
2996
|
+
applyRcsSmsFallbackTagExtraction();
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
const contentToExtract = getContentForTagExtraction();
|
|
2329
3001
|
const tags = contentToExtract.match(/{{[^}]+}}/g) || [];
|
|
2330
3002
|
const hasPersonalizationTags = tags.some((tag) => !tag.includes(UNSUBSCRIBE_TAG_NAME));
|
|
3003
|
+
const onlyUnsubscribe = !hasPersonalizationTags && tags.length === 1 && tags[0].includes(UNSUBSCRIBE_TAG_NAME);
|
|
2331
3004
|
|
|
2332
|
-
if (
|
|
2333
|
-
// If only unsubscribe tag is present, show noTagsExtracted message
|
|
3005
|
+
if (onlyUnsubscribe) {
|
|
2334
3006
|
setTagsExtracted(false);
|
|
2335
3007
|
setRequiredTags([]);
|
|
2336
3008
|
setOptionalTags([]);
|
|
@@ -2338,10 +3010,9 @@ const CommonTestAndPreview = (props) => {
|
|
|
2338
3010
|
return;
|
|
2339
3011
|
}
|
|
2340
3012
|
|
|
2341
|
-
// Extract tags
|
|
2342
3013
|
setTagsExtracted(true);
|
|
2343
3014
|
const { templateSubject, templateContent } = prepareTagExtractionPayload(
|
|
2344
|
-
|
|
3015
|
+
activeChannelForActions,
|
|
2345
3016
|
formData || {},
|
|
2346
3017
|
contentToExtract
|
|
2347
3018
|
);
|
|
@@ -2361,7 +3032,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2361
3032
|
const handleSendTestMessage = () => {
|
|
2362
3033
|
const allUserIds = [];
|
|
2363
3034
|
selectedTestEntities.forEach((entityId) => {
|
|
2364
|
-
const group = testGroups.find((
|
|
3035
|
+
const group = testGroups.find((testGroup) => testGroup.groupId === entityId);
|
|
2365
3036
|
if (group) {
|
|
2366
3037
|
allUserIds.push(...group.userIds);
|
|
2367
3038
|
} else {
|
|
@@ -2370,14 +3041,29 @@ const CommonTestAndPreview = (props) => {
|
|
|
2370
3041
|
});
|
|
2371
3042
|
const uniqueUserIds = [...new Set(allUserIds)];
|
|
2372
3043
|
|
|
2373
|
-
|
|
3044
|
+
const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS].includes(channel)
|
|
3045
|
+
? testPreviewDeliverySettings[channel]
|
|
3046
|
+
: null;
|
|
3047
|
+
|
|
3048
|
+
// createMessageMeta must match the creative channel and full creative (RCS + SMS fallback in one meta).
|
|
3049
|
+
// Do not use activeChannelForActions / activeContentForActions — those follow the RCS vs Fallback SMS *preview* tab.
|
|
2374
3050
|
const initialPayload = prepareTestMessagePayload(
|
|
2375
3051
|
channel,
|
|
2376
3052
|
formData || content || {},
|
|
2377
3053
|
getCurrentContent,
|
|
2378
3054
|
customValues,
|
|
2379
3055
|
uniqueUserIds,
|
|
2380
|
-
previewData
|
|
3056
|
+
previewData,
|
|
3057
|
+
deliveryOverride,
|
|
3058
|
+
channel === CHANNELS.RCS
|
|
3059
|
+
? {
|
|
3060
|
+
smsFallbackTemplateContent:
|
|
3061
|
+
smsFallbackTextForTagExtraction
|
|
3062
|
+
|| smsFallbackContent?.templateContent
|
|
3063
|
+
|| smsFallbackContent?.content
|
|
3064
|
+
|| '',
|
|
3065
|
+
}
|
|
3066
|
+
: {},
|
|
2381
3067
|
);
|
|
2382
3068
|
|
|
2383
3069
|
actions.createMessageMetaRequested(
|
|
@@ -2410,11 +3096,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
2410
3096
|
// ============================================
|
|
2411
3097
|
// RENDER HELPER FUNCTIONS
|
|
2412
3098
|
// ============================================
|
|
2413
|
-
|
|
2414
3099
|
const renderLeftPanelContent = () => (
|
|
2415
3100
|
<LeftPanelContent
|
|
2416
|
-
isExtractingTags={isExtractingTags}
|
|
2417
|
-
extractedTags={
|
|
3101
|
+
isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
|
|
3102
|
+
extractedTags={leftPanelExtractedTags}
|
|
2418
3103
|
selectedCustomer={selectedCustomer}
|
|
2419
3104
|
handleCustomerSelect={handleCustomerSelect}
|
|
2420
3105
|
handleSearchCustomer={handleSearchCustomer}
|
|
@@ -2432,15 +3117,29 @@ const CommonTestAndPreview = (props) => {
|
|
|
2432
3117
|
|
|
2433
3118
|
const renderCustomValuesEditor = () => (
|
|
2434
3119
|
<CustomValuesEditor
|
|
2435
|
-
isExtractingTags={isExtractingTags}
|
|
3120
|
+
isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
|
|
2436
3121
|
isUpdatePreviewDisabled={isUpdatePreviewDisabled}
|
|
2437
3122
|
showJSON={showJSON}
|
|
2438
3123
|
setShowJSON={setShowJSON}
|
|
2439
3124
|
customValues={customValues}
|
|
2440
3125
|
handleJSONTextChange={handleJSONTextChange}
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
3126
|
+
sections={[
|
|
3127
|
+
{
|
|
3128
|
+
key: channel,
|
|
3129
|
+
title:
|
|
3130
|
+
channel === CHANNELS.RCS
|
|
3131
|
+
? messages.rcsTagsSectionTitle
|
|
3132
|
+
: messages[`${channel}TagsSectionTitle`],
|
|
3133
|
+
requiredTags,
|
|
3134
|
+
optionalTags,
|
|
3135
|
+
},
|
|
3136
|
+
{
|
|
3137
|
+
key: PREVIEW_TAB_SMS_FALLBACK,
|
|
3138
|
+
title: channel === CHANNELS.RCS && smsFallbackContent?.templateContent ? messages.smsFallbackTagsSectionTitle : null,
|
|
3139
|
+
requiredTags: smsFallbackRequiredTags,
|
|
3140
|
+
optionalTags: smsFallbackOptionalTags,
|
|
3141
|
+
},
|
|
3142
|
+
]}
|
|
2444
3143
|
handleCustomValueChange={handleCustomValueChange}
|
|
2445
3144
|
handleDiscardCustomValues={handleDiscardCustomValues}
|
|
2446
3145
|
handleUpdatePreview={handleUpdatePreview}
|
|
@@ -2449,6 +3148,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
2449
3148
|
/>
|
|
2450
3149
|
);
|
|
2451
3150
|
|
|
3151
|
+
const handleSaveDeliverySettings = (values) => {
|
|
3152
|
+
setTestPreviewDeliverySettings((prev) => ({
|
|
3153
|
+
...prev,
|
|
3154
|
+
[channel]: values,
|
|
3155
|
+
}));
|
|
3156
|
+
};
|
|
3157
|
+
|
|
2452
3158
|
const renderSendTestMessage = () => (
|
|
2453
3159
|
<SendTestMessage
|
|
2454
3160
|
isFetchingTestCustomers={isFetchingTestCustomers}
|
|
@@ -2457,11 +3163,19 @@ const CommonTestAndPreview = (props) => {
|
|
|
2457
3163
|
handleTestEntitiesChange={handleTestEntitiesChange}
|
|
2458
3164
|
selectedTestEntities={selectedTestEntities}
|
|
2459
3165
|
handleSendTestMessage={handleSendTestMessage}
|
|
2460
|
-
formData={
|
|
3166
|
+
formData={formDataForSendTest}
|
|
2461
3167
|
content={getCurrentContent}
|
|
2462
3168
|
channel={channel}
|
|
2463
3169
|
isSendingTestMessage={isSendingTestMessage}
|
|
2464
3170
|
formatMessage={formatMessage}
|
|
3171
|
+
deliverySettings={testPreviewDeliverySettings[channel]}
|
|
3172
|
+
senderDetailsByChannel={senderDetailsByChannel}
|
|
3173
|
+
wecrmAccounts={wecrmAccounts}
|
|
3174
|
+
onSaveDeliverySettings={handleSaveDeliverySettings}
|
|
3175
|
+
isLoadingSenderDetails={isLoadingSenderDetails}
|
|
3176
|
+
smsTraiDltEnabled={smsTraiDltEnabled}
|
|
3177
|
+
registeredSenderIds={registeredSenderIds}
|
|
3178
|
+
isChannelSmsFallbackPreviewEnabled={channel === CHANNELS.RCS && !!smsFallbackContent?.templateContent}
|
|
2465
3179
|
/>
|
|
2466
3180
|
);
|
|
2467
3181
|
|
|
@@ -2562,6 +3276,10 @@ CommonTestAndPreview.propTypes = {
|
|
|
2562
3276
|
})),
|
|
2563
3277
|
isSendingTestMessage: PropTypes.bool.isRequired,
|
|
2564
3278
|
intl: PropTypes.object.isRequired,
|
|
3279
|
+
senderDetailsByChannel: PropTypes.object,
|
|
3280
|
+
wecrmAccounts: PropTypes.array,
|
|
3281
|
+
isLoadingSenderDetails: PropTypes.bool,
|
|
3282
|
+
orgUnitId: PropTypes.number,
|
|
2565
3283
|
|
|
2566
3284
|
// Email-specific props
|
|
2567
3285
|
beeInstance: PropTypes.object,
|
|
@@ -2597,6 +3315,10 @@ CommonTestAndPreview.defaultProps = {
|
|
|
2597
3315
|
rcsOrientation: null,
|
|
2598
3316
|
rcsIosPreview: false,
|
|
2599
3317
|
templateLayoutType: null,
|
|
3318
|
+
senderDetailsByChannel: {},
|
|
3319
|
+
wecrmAccounts: [],
|
|
3320
|
+
isLoadingSenderDetails: false,
|
|
3321
|
+
orgUnitId: -1,
|
|
2600
3322
|
};
|
|
2601
3323
|
|
|
2602
3324
|
// ============================================
|