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
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/* eslint-disable no-unused-expressions */
|
|
2
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
3
3
|
import { bindActionCreators } from 'redux';
|
|
4
4
|
import { createStructuredSelector } from 'reselect';
|
|
5
|
-
import {
|
|
5
|
+
import { FormattedMessage } from 'react-intl';
|
|
6
6
|
import get from 'lodash/get';
|
|
7
7
|
import isEmpty from 'lodash/isEmpty';
|
|
8
8
|
import cloneDeep from 'lodash/cloneDeep';
|
|
9
9
|
import isNil from 'lodash/isNil';
|
|
10
|
-
import styled from 'styled-components';
|
|
11
10
|
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
12
11
|
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
13
12
|
import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
|
|
@@ -22,44 +21,34 @@ import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
|
|
|
22
21
|
import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
|
|
23
22
|
import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
24
23
|
import CapImage from '@capillarytech/cap-ui-library/CapImage';
|
|
25
|
-
import CapCard from '@capillarytech/cap-ui-library/CapCard';
|
|
26
24
|
import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
|
|
27
25
|
import CapSelect from '@capillarytech/cap-ui-library/CapSelect';
|
|
28
|
-
import CapCustomCard from '@capillarytech/cap-ui-library/CapCustomCard';
|
|
29
|
-
import CapDropdown from '@capillarytech/cap-ui-library/CapDropdown';
|
|
30
|
-
import CapMenu from '@capillarytech/cap-ui-library/CapMenu';
|
|
31
26
|
import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
|
|
32
|
-
import CapTooltipWithInfo from '@capillarytech/cap-ui-library/CapTooltipWithInfo';
|
|
33
27
|
import CapError from '@capillarytech/cap-ui-library/CapError';
|
|
34
|
-
import CapCheckbox from '@capillarytech/cap-ui-library/CapCheckbox';
|
|
35
28
|
import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
|
|
36
|
-
import CapLink from '@capillarytech/cap-ui-library/CapLink';
|
|
37
|
-
|
|
38
|
-
import {
|
|
39
|
-
CAP_G01,
|
|
40
|
-
CAP_SPACE_04,
|
|
41
|
-
CAP_SPACE_16,
|
|
42
|
-
CAP_SPACE_24,
|
|
43
|
-
CAP_SPACE_28,
|
|
44
|
-
CAP_SPACE_32,
|
|
45
|
-
CAP_WHITE,
|
|
46
|
-
CAP_SECONDARY,
|
|
47
|
-
} from '@capillarytech/cap-ui-library/styled/variables';
|
|
48
29
|
|
|
49
30
|
import CapVideoUpload from '../../v2Components/CapVideoUpload';
|
|
50
31
|
import * as globalActions from '../Cap/actions';
|
|
51
32
|
import CapActionButton from '../../v2Components/CapActionButton';
|
|
52
33
|
import { makeSelectRcs, makeSelectAccount } from './selectors';
|
|
53
|
-
import { DATE_DISPLAY_FORMAT, TIME_DISPLAY_FORMAT } from '../App/constants';
|
|
54
34
|
import {
|
|
55
35
|
isLoadingMetaEntities,
|
|
56
36
|
makeSelectMetaEntities,
|
|
57
37
|
setInjectedTags,
|
|
58
|
-
selectCurrentOrgDetails,
|
|
59
38
|
} from '../Cap/selectors';
|
|
60
39
|
import * as RcsActions from './actions';
|
|
40
|
+
import { isAiContentBotDisabled } from '../../utils/common';
|
|
61
41
|
import * as TemplatesActions from '../Templates/actions';
|
|
62
42
|
import './index.scss';
|
|
43
|
+
import {
|
|
44
|
+
normalizeLibraryLoadedTitleDesc,
|
|
45
|
+
mergeRcsSmsFallBackContentFromDetails,
|
|
46
|
+
mergeRcsSmsFallbackVarMapLayers,
|
|
47
|
+
pickFirstSmsFallbackTemplateString,
|
|
48
|
+
syncCardVarMappedSemanticsFromSlots,
|
|
49
|
+
hasMeaningfulSmsFallbackShape,
|
|
50
|
+
getLibrarySmsFallbackApiBaselineFromTemplateData,
|
|
51
|
+
} from './rcsLibraryHydrationUtils';
|
|
63
52
|
import {
|
|
64
53
|
RCS,
|
|
65
54
|
SMS,
|
|
@@ -76,14 +65,11 @@ import {
|
|
|
76
65
|
RCS_IMG_SIZE,
|
|
77
66
|
RCS_DLT_MODE,
|
|
78
67
|
CTA,
|
|
79
|
-
AI_CONTENT_BOT_DISABLED,
|
|
80
68
|
RCS_STATUSES,
|
|
81
69
|
TITLE_TEXT,
|
|
82
70
|
MESSAGE_TEXT,
|
|
83
71
|
ALLOWED_EXTENSIONS_VIDEO_REGEX,
|
|
84
72
|
RCS_VIDEO_SIZE,
|
|
85
|
-
TEMPLATE_HEADER_MAX_LENGTH,
|
|
86
|
-
TEMPLATE_MESSAGE_MAX_LENGTH,
|
|
87
73
|
RCS_THUMBNAIL_MIN_SIZE,
|
|
88
74
|
RCS_THUMBNAIL_MAX_SIZE,
|
|
89
75
|
contentType,
|
|
@@ -96,35 +82,45 @@ import {
|
|
|
96
82
|
MAX_BUTTONS,
|
|
97
83
|
INITIAL_SUGGESTIONS_DATA_STOP,
|
|
98
84
|
RCS_BUTTON_TYPES,
|
|
99
|
-
titletype,
|
|
100
|
-
descType,
|
|
101
85
|
STANDALONE,
|
|
102
86
|
VERTICAL,
|
|
103
87
|
SMALL,
|
|
104
88
|
MEDIUM,
|
|
105
89
|
RICHCARD,
|
|
90
|
+
RCS_NUMERIC_VAR_NAME_REGEX,
|
|
91
|
+
RCS_TAG_AREA_FIELD_TITLE,
|
|
92
|
+
RCS_TAG_AREA_FIELD_DESC,
|
|
106
93
|
} from './constants';
|
|
107
94
|
import globalMessages from '../Cap/messages';
|
|
108
95
|
import messages from './messages';
|
|
109
96
|
import creativesMessages from '../CreativesContainer/messages';
|
|
110
97
|
import withCreatives from '../../hoc/withCreatives';
|
|
111
98
|
import UnifiedPreview from '../../v2Components/CommonTestAndPreview/UnifiedPreview';
|
|
112
|
-
import
|
|
99
|
+
import VarSegmentMessageEditor from '../../v2Components/VarSegmentMessageEditor';
|
|
100
|
+
import { ANDROID, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
|
|
113
101
|
import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
|
|
102
|
+
import { splitTemplateVarString } from '../../utils/templateVarUtils';
|
|
114
103
|
import CapImageUpload from '../../v2Components/CapImageUpload';
|
|
115
|
-
import addCreativesIcon from '../Assets/images/addCreativesIllustration.svg';
|
|
116
104
|
import Templates from '../Templates';
|
|
117
105
|
import SmsTraiEdit from '../SmsTrai/Edit';
|
|
106
|
+
import SmsFallback from '../../v2Components/SmsFallback';
|
|
107
|
+
import { CHANNELS_TO_HIDE_FOR_SMS_ONLY } from '../../v2Components/SmsFallback/constants';
|
|
118
108
|
import TagList from '../TagList';
|
|
119
109
|
import { validateTags } from '../../utils/tagValidations';
|
|
120
|
-
import {
|
|
110
|
+
import { isTraiDLTEnable } from '../../utils/common';
|
|
121
111
|
import { isTagIncluded } from '../../utils/commonUtils';
|
|
122
112
|
import injectReducer from '../../utils/injectReducer';
|
|
123
113
|
import v2RcsReducer from './reducer';
|
|
124
|
-
import {
|
|
125
|
-
|
|
114
|
+
import {
|
|
115
|
+
areAllRcsSmsFallbackVarSlotsFilled,
|
|
116
|
+
buildRcsNumericMustachePlaceholderRegex,
|
|
117
|
+
getTemplateStatusType,
|
|
118
|
+
normalizeCardVarMapped,
|
|
119
|
+
coalesceCardVarMappedToTemplate,
|
|
120
|
+
resolveCardVarMappedSlotValue,
|
|
121
|
+
sanitizeCardVarMappedValue,
|
|
122
|
+
} from './utils';
|
|
126
123
|
|
|
127
|
-
const { Group: CapCheckboxGroup } = CapCheckbox;
|
|
128
124
|
export const Rcs = (props) => {
|
|
129
125
|
const {
|
|
130
126
|
intl,
|
|
@@ -138,18 +134,17 @@ export const Rcs = (props) => {
|
|
|
138
134
|
templatesActions,
|
|
139
135
|
globalActions,
|
|
140
136
|
location,
|
|
141
|
-
handleClose,
|
|
142
137
|
getDefaultTags,
|
|
143
138
|
supportedTags,
|
|
144
139
|
metaEntities,
|
|
145
140
|
injectedTags,
|
|
146
141
|
loadingTags,
|
|
147
142
|
getFormData,
|
|
148
|
-
isDltEnabled,
|
|
149
143
|
smsRegister,
|
|
144
|
+
orgUnitId,
|
|
150
145
|
selectedOfferDetails,
|
|
151
|
-
currentOrgDetails,
|
|
152
146
|
eventContextTags,
|
|
147
|
+
waitEventContextTags,
|
|
153
148
|
accountData = {},
|
|
154
149
|
// TestAndPreviewSlidebox props
|
|
155
150
|
showTestAndPreviewSlidebox: propsShowTestAndPreviewSlidebox,
|
|
@@ -158,8 +153,8 @@ export const Rcs = (props) => {
|
|
|
158
153
|
} = props || {};
|
|
159
154
|
const { formatMessage } = intl;
|
|
160
155
|
const { TextArea } = CapInput;
|
|
161
|
-
const { CapCustomCardList } = CapCustomCard;
|
|
162
156
|
const [isEditFlow, setEditFlow] = useState(false);
|
|
157
|
+
const isEditLike = isEditFlow || !isFullMode;
|
|
163
158
|
const [tags, updateTags] = useState([]);
|
|
164
159
|
const [spin, setSpin] = useState(false);
|
|
165
160
|
//template
|
|
@@ -168,112 +163,71 @@ export const Rcs = (props) => {
|
|
|
168
163
|
const [templateMediaType, setTemplateMediaType] = useState(
|
|
169
164
|
RCS_MEDIA_TYPES.NONE,
|
|
170
165
|
);
|
|
171
|
-
const [templateRejectionReason, setTemplateRejectionReason] = useState(null);
|
|
172
166
|
const [templateTitle, setTemplateTitle] = useState('');
|
|
173
167
|
const [templateDesc, setTemplateDesc] = useState('');
|
|
174
168
|
const [templateDescError, setTemplateDescError] = useState(false);
|
|
175
169
|
const [templateStatus, setTemplateStatus] = useState('');
|
|
176
|
-
const [templateDate, setTemplateDate] = useState('');
|
|
177
|
-
//fallback
|
|
178
|
-
const [fallbackMessage, setFallbackMessage] = useState('');
|
|
179
|
-
const [fallbackMessageError, setFallbackMessageError] = useState(false);
|
|
180
170
|
//fallback dlt
|
|
181
171
|
const [showDltContainer, setShowDltContainer] = useState(false);
|
|
182
172
|
const [dltMode, setDltMode] = useState('');
|
|
183
173
|
const [dltEditData, setDltEditData] = useState({});
|
|
184
|
-
|
|
185
|
-
const [
|
|
186
|
-
const [dltPreviewData, setDltPreviewData] = useState('');
|
|
174
|
+
/** `undefined` = not hydrated yet; `null` = no fallback / user removed template; object = selected fallback */
|
|
175
|
+
const [smsFallbackData, setSmsFallbackData] = useState(undefined);
|
|
187
176
|
const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS_DATA_STOP);
|
|
188
|
-
const
|
|
177
|
+
const buttonType = RCS_BUTTON_TYPES.NONE;
|
|
189
178
|
const [suggestionError, setSuggestionError] = useState(true);
|
|
190
179
|
const [templateType, setTemplateType] = useState('text_message');
|
|
191
|
-
const [templateHeader, setTemplateHeader] = useState('');
|
|
192
|
-
const [templateMessage, setTemplateMessage] = useState('');
|
|
193
|
-
const [templateHeaderError, setTemplateHeaderError] = useState('');
|
|
194
|
-
const [templateMessageError, setTemplateMessageError] = useState('');
|
|
195
|
-
const validVarRegex = /\{\{(\d+)\}\}/g;
|
|
196
|
-
const [updatedTitleData, setUpdatedTitleData] = useState([]);
|
|
197
|
-
const [updatedDescData, setUpdatedDescData] = useState([]);
|
|
198
180
|
const [titleVarMappedData, setTitleVarMappedData] = useState({});
|
|
199
181
|
const [descVarMappedData, setDescVarMappedData] = useState({});
|
|
200
182
|
const [titleTextAreaId, setTitleTextAreaId] = useState();
|
|
201
183
|
const [descTextAreaId, setDescTextAreaId] = useState();
|
|
202
184
|
const [assetList, setAssetList] = useState({});
|
|
203
|
-
const [assetListImage, setAssetListImage] = useState('');
|
|
204
185
|
const [rcsImageSrc, updateRcsImageSrc] = useState('');
|
|
205
186
|
const [rcsVideoSrc, setRcsVideoSrc] = useState({});
|
|
206
187
|
const [rcsThumbnailSrc, setRcsThumbnailSrc] = useState('');
|
|
207
188
|
const [selectedDimension, setSelectedDimension] = useState(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
208
|
-
const [imageError, setImageError] = useState(null);
|
|
209
189
|
const [templateTitleError, setTemplateTitleError] = useState(false);
|
|
210
190
|
const [cardVarMapped, setCardVarMapped] = useState({});
|
|
191
|
+
/** Bump when hydrated cardVarMapped payload changes so VarSegment TextAreas remount with fresh valueMap (controlled-input sync). */
|
|
192
|
+
const [rcsVarSegmentEditorRemountKey, setRcsVarSegmentEditorRemountKey] = useState(0);
|
|
193
|
+
const lastHydratedRcsCardVarSignatureRef = useRef(null);
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Hydrate only from template payload — not from full `rcsData`. Uploads/asset updates change `rcsData`
|
|
197
|
+
* without changing `templateDetails`; re-running hydration then cleared `smsFallbackData`, so the SMS
|
|
198
|
+
* fallback card / content stopped appearing until re-selected.
|
|
199
|
+
*/
|
|
200
|
+
const rcsHydrationDetails = useMemo(
|
|
201
|
+
() => (isFullMode ? rcsData?.templateDetails : templateData),
|
|
202
|
+
[isFullMode, rcsData?.templateDetails, templateData],
|
|
203
|
+
);
|
|
211
204
|
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
226
|
-
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
227
|
-
|
|
228
|
-
// Build media preview object (same pattern as getRcsPreview)
|
|
229
|
-
const mediaPreview = {};
|
|
230
|
-
if (isMediaTypeImage && rcsImageSrc) {
|
|
231
|
-
mediaPreview.rcsImageSrc = rcsImageSrc;
|
|
232
|
-
}
|
|
233
|
-
if (isMediaTypeVideo && !isMediaTypeText) {
|
|
234
|
-
// For video, use thumbnailSrc as rcsVideoSrc (same as getRcsPreview line 2104)
|
|
235
|
-
if (rcsThumbnailSrc) {
|
|
236
|
-
mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
|
|
237
|
-
} else if (rcsVideoSrc?.videoSrc) {
|
|
238
|
-
mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
|
|
239
|
-
}
|
|
240
|
-
// Also include thumbnailSrc separately if available
|
|
241
|
-
if (rcsThumbnailSrc) {
|
|
242
|
-
mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
|
|
205
|
+
/** Skip duplicate /meta/TAG fetches: same query is triggered from (1) useEffect below, (2) title TagList mount, (3) description TagList mount — each calls getTagsforContext('Outbound'). */
|
|
206
|
+
const lastTagSchemaQueryKeyRef = useRef(null);
|
|
207
|
+
/**
|
|
208
|
+
* Library: parent often passes a new `templateData` object reference every render. Re-applying the same
|
|
209
|
+
* SMS fallback snapshot was resetting `smsFallbackData` and caused VarSegment inputs to flicker old/new.
|
|
210
|
+
*/
|
|
211
|
+
const lastSmsFallbackHydrationKeyRef = useRef(null);
|
|
212
|
+
|
|
213
|
+
const fetchTagSchemaIfNewQuery = useCallback(
|
|
214
|
+
(query) => {
|
|
215
|
+
const key = JSON.stringify(query);
|
|
216
|
+
if (lastTagSchemaQueryKeyRef.current === key) {
|
|
217
|
+
return;
|
|
243
218
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
// templateDesc is used for ALL types (text message body or rich card description)
|
|
250
|
-
// For UnifiedPreview, we map templateTitle -> templateHeader and templateDesc -> templateMessage
|
|
251
|
-
const contentObj = {
|
|
252
|
-
// Map templateTitle to templateHeader and templateDesc to templateMessage
|
|
253
|
-
templateHeader: templateTitle,
|
|
254
|
-
templateMessage: templateDesc,
|
|
255
|
-
...mediaPreview,
|
|
256
|
-
...(suggestions.length > 0 && {
|
|
257
|
-
suggestions: suggestions,
|
|
258
|
-
}),
|
|
259
|
-
};
|
|
219
|
+
lastTagSchemaQueryKeyRef.current = key;
|
|
220
|
+
globalActions.fetchSchemaForEntity(query);
|
|
221
|
+
},
|
|
222
|
+
[globalActions],
|
|
223
|
+
);
|
|
260
224
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
templateMediaType,
|
|
264
|
-
templateTitle,
|
|
265
|
-
templateDesc,
|
|
266
|
-
rcsImageSrc,
|
|
267
|
-
rcsVideoSrc,
|
|
268
|
-
rcsThumbnailSrc,
|
|
269
|
-
suggestions,
|
|
270
|
-
selectedDimension,
|
|
271
|
-
]);
|
|
225
|
+
// TestAndPreviewSlidebox state
|
|
226
|
+
const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
|
|
272
227
|
|
|
273
228
|
// Handle Test and Preview button click
|
|
274
229
|
const handleTestAndPreview = useCallback(() => {
|
|
275
230
|
setShowTestAndPreviewSlidebox(true);
|
|
276
|
-
setIsTestAndPreviewMode(true);
|
|
277
231
|
if (propsHandleTestAndPreview) {
|
|
278
232
|
propsHandleTestAndPreview();
|
|
279
233
|
}
|
|
@@ -282,23 +236,24 @@ export const Rcs = (props) => {
|
|
|
282
236
|
// Handle close Test and Preview slidebox
|
|
283
237
|
const handleCloseTestAndPreview = useCallback(() => {
|
|
284
238
|
setShowTestAndPreviewSlidebox(false);
|
|
285
|
-
setIsTestAndPreviewMode(false);
|
|
286
239
|
if (propsHandleCloseTestAndPreview) {
|
|
287
240
|
propsHandleCloseTestAndPreview();
|
|
288
241
|
}
|
|
289
242
|
}, [propsHandleCloseTestAndPreview]);
|
|
290
243
|
|
|
291
|
-
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
};
|
|
244
|
+
/** Merge editor slot map into `smsFallbackData` (like `cardVarMapped` for title/desc). */
|
|
245
|
+
const handleSmsFallbackEditorStateChange = useCallback((patch) => {
|
|
246
|
+
setSmsFallbackData((prev) => {
|
|
247
|
+
if (!patch || typeof patch !== 'object') return prev;
|
|
248
|
+
// Merge even when `prev` is null so tag/slot updates apply on top of `smsFromApiShape` in createPayload.
|
|
249
|
+
return { ...(prev || {}), ...patch };
|
|
250
|
+
});
|
|
251
|
+
}, []);
|
|
300
252
|
|
|
253
|
+
/** RCS template save / edit API: `rcsContent.accountId` must stay `sourceAccountIdentifier` (pairs with accessToken). */
|
|
301
254
|
const [accountId, setAccountId] = useState('');
|
|
255
|
+
/** WeCRM list row `id` — only for CommonTestAndPreview → createMessageMeta payload, not for template save. */
|
|
256
|
+
const [wecrmAccountId, setWecrmAccountId] = useState('');
|
|
302
257
|
const [accessToken, setAccessToken] = useState('');
|
|
303
258
|
const [hostName, setHostName] = useState('');
|
|
304
259
|
const [accountName, setAccountName] = useState('');
|
|
@@ -306,14 +261,23 @@ export const Rcs = (props) => {
|
|
|
306
261
|
const accountObj = accountData.selectedRcsAccount || {};
|
|
307
262
|
if (!isEmpty(accountObj)) {
|
|
308
263
|
const {
|
|
264
|
+
id: wecrmId,
|
|
309
265
|
sourceAccountIdentifier = '',
|
|
310
266
|
configs = {},
|
|
311
267
|
} = accountObj;
|
|
312
|
-
|
|
313
268
|
setAccountId(sourceAccountIdentifier);
|
|
269
|
+
setWecrmAccountId(
|
|
270
|
+
wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
|
|
271
|
+
);
|
|
314
272
|
setAccessToken(configs.accessToken || '');
|
|
315
273
|
setHostName(accountObj.hostName || '');
|
|
316
274
|
setAccountName(accountObj.name || '');
|
|
275
|
+
} else {
|
|
276
|
+
setAccountId('');
|
|
277
|
+
setWecrmAccountId('');
|
|
278
|
+
setAccessToken('');
|
|
279
|
+
setHostName('');
|
|
280
|
+
setAccountName('');
|
|
317
281
|
}
|
|
318
282
|
}, [accountData.selectedRcsAccount]);
|
|
319
283
|
|
|
@@ -331,10 +295,7 @@ export const Rcs = (props) => {
|
|
|
331
295
|
label: formatMessage(messages.mediaVideo),
|
|
332
296
|
},
|
|
333
297
|
];
|
|
334
|
-
const
|
|
335
|
-
const isAiContentBotDisabled = accessibleFeatures?.includes(
|
|
336
|
-
AI_CONTENT_BOT_DISABLED
|
|
337
|
-
);
|
|
298
|
+
const aiContentBotDisabled = isAiContentBotDisabled();
|
|
338
299
|
|
|
339
300
|
const updateButtonChange = (data, index) => {
|
|
340
301
|
if (data && data.text) {
|
|
@@ -372,7 +333,9 @@ export const Rcs = (props) => {
|
|
|
372
333
|
if (isFullMode) return;
|
|
373
334
|
if (loadingTags || !tags || tags.length === 0) return;
|
|
374
335
|
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
375
|
-
const
|
|
336
|
+
const slotOffset =
|
|
337
|
+
type === TITLE_TEXT ? 0 : (templateTitle ? templateTitle.match(rcsVarRegex) || [] : []).length;
|
|
338
|
+
const resolved = resolveTemplateWithMap(templateStr, slotOffset); // placeholders -> mapped value (or '')
|
|
376
339
|
if (!resolved) {
|
|
377
340
|
if (type === TITLE_TEXT) setTemplateTitleError(false);
|
|
378
341
|
if (type === MESSAGE_TEXT) setTemplateDescError(false);
|
|
@@ -394,23 +357,15 @@ export const Rcs = (props) => {
|
|
|
394
357
|
const validationResponse =
|
|
395
358
|
validateTags({
|
|
396
359
|
content: contentForValidation,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const errorMsg =
|
|
407
|
-
(unsupportedTagsLengthCheck &&
|
|
408
|
-
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
409
|
-
unsupportedTags: validationResponse.unsupportedTags,
|
|
410
|
-
})) ||
|
|
411
|
-
(validationResponse.isBraceError &&
|
|
412
|
-
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
413
|
-
false;
|
|
360
|
+
tagsParam: tags,
|
|
361
|
+
location,
|
|
362
|
+
tagModule: getDefaultTags,
|
|
363
|
+
isFullMode,
|
|
364
|
+
}) || {};
|
|
365
|
+
const errorMsg =
|
|
366
|
+
(validationResponse?.isBraceError &&
|
|
367
|
+
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
368
|
+
false;
|
|
414
369
|
if (type === TITLE_TEXT) setTemplateTitleError(errorMsg);
|
|
415
370
|
if (type === MESSAGE_TEXT) setTemplateDescError(errorMsg);
|
|
416
371
|
};
|
|
@@ -425,13 +380,41 @@ export const Rcs = (props) => {
|
|
|
425
380
|
|
|
426
381
|
const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
|
|
427
382
|
|
|
428
|
-
const
|
|
383
|
+
const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
|
|
384
|
+
|
|
385
|
+
/** Global slot index (0-based, title vars then desc) for a VarSegmentMessageEditor `id` (`{{tok}}_segIdx`), or null if not found. */
|
|
386
|
+
const getGlobalSlotIndexForRcsFieldId = (varSegmentCompositeId, fieldTemplateStr, fieldType) => {
|
|
387
|
+
const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
|
|
388
|
+
const offset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
|
|
389
|
+
const templateSegments = splitTemplateVarStringRcs(fieldTemplateStr ?? '');
|
|
390
|
+
let varOrdinal = 0;
|
|
391
|
+
for (let segmentIndexInField = 0; segmentIndexInField < templateSegments.length; segmentIndexInField += 1) {
|
|
392
|
+
const segmentToken = templateSegments[segmentIndexInField];
|
|
393
|
+
if (rcsVarTestRegex.test(segmentToken)) {
|
|
394
|
+
if (`${segmentToken}_${segmentIndexInField}` === varSegmentCompositeId) {
|
|
395
|
+
return offset + varOrdinal;
|
|
396
|
+
}
|
|
397
|
+
varOrdinal += 1;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return null;
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
|
|
429
404
|
if (!str) return '';
|
|
430
|
-
const arr =
|
|
405
|
+
const arr = splitTemplateVarStringRcs(str);
|
|
406
|
+
let varOrdinal = 0;
|
|
431
407
|
return arr.map((elem) => {
|
|
432
408
|
if (rcsVarTestRegex.test(elem)) {
|
|
433
409
|
const key = getVarNameFromToken(elem);
|
|
434
|
-
const
|
|
410
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
411
|
+
varOrdinal += 1;
|
|
412
|
+
const v = resolveCardVarMappedSlotValue(
|
|
413
|
+
cardVarMapped,
|
|
414
|
+
key,
|
|
415
|
+
globalSlot,
|
|
416
|
+
isEditLike,
|
|
417
|
+
);
|
|
435
418
|
if (isNil(v) || String(v)?.trim?.() === '') return elem;
|
|
436
419
|
return String(v);
|
|
437
420
|
}
|
|
@@ -439,107 +422,154 @@ export const Rcs = (props) => {
|
|
|
439
422
|
}).join('');
|
|
440
423
|
};
|
|
441
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Content for TestAndPreviewSlidebox — apply cardVarMapped whenever the slot editor is shown
|
|
427
|
+
* (VarSegmentMessageEditor: isEditFlow || !isFullMode). Full-mode create without edit uses plain
|
|
428
|
+
* TextArea only — no mapping. Full-mode + edit uses slots; !isFullMode alone is not enough.
|
|
429
|
+
*/
|
|
430
|
+
const getTemplateContent = useCallback(() => {
|
|
431
|
+
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
432
|
+
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
433
|
+
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
442
434
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
tokens.forEach((t) => {
|
|
454
|
-
const name = getVarNameFromToken(t);
|
|
455
|
-
if (name && !Object.prototype.hasOwnProperty.call(next, name)) {
|
|
456
|
-
next[name] = '';
|
|
457
|
-
changed = true;
|
|
458
|
-
}
|
|
459
|
-
});
|
|
460
|
-
return changed ? next : prev;
|
|
461
|
-
});
|
|
462
|
-
}, [isFullMode, templateTitle, templateDesc]);
|
|
435
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
436
|
+
const titleVarCountForResolve = isMediaTypeText
|
|
437
|
+
? 0
|
|
438
|
+
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
439
|
+
const resolvedTitle = isMediaTypeText
|
|
440
|
+
? ''
|
|
441
|
+
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
442
|
+
const resolvedDesc = isSlotMappingMode
|
|
443
|
+
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
444
|
+
: templateDesc;
|
|
463
445
|
|
|
446
|
+
const mediaPreview = {};
|
|
447
|
+
if (isMediaTypeImage && rcsImageSrc) {
|
|
448
|
+
mediaPreview.rcsImageSrc = rcsImageSrc;
|
|
449
|
+
}
|
|
450
|
+
if (isMediaTypeVideo && !isMediaTypeText) {
|
|
451
|
+
if (rcsThumbnailSrc) {
|
|
452
|
+
mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
|
|
453
|
+
} else if (rcsVideoSrc?.videoSrc) {
|
|
454
|
+
mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
|
|
455
|
+
}
|
|
456
|
+
if (rcsThumbnailSrc) {
|
|
457
|
+
mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const contentObj = {
|
|
462
|
+
templateHeader: resolvedTitle,
|
|
463
|
+
templateMessage: resolvedDesc,
|
|
464
|
+
...mediaPreview,
|
|
465
|
+
...(suggestions.length > 0 && {
|
|
466
|
+
suggestions: suggestions,
|
|
467
|
+
}),
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
return contentObj;
|
|
471
|
+
}, [
|
|
472
|
+
templateMediaType,
|
|
473
|
+
templateTitle,
|
|
474
|
+
templateDesc,
|
|
475
|
+
rcsImageSrc,
|
|
476
|
+
rcsVideoSrc,
|
|
477
|
+
rcsThumbnailSrc,
|
|
478
|
+
suggestions,
|
|
479
|
+
selectedDimension,
|
|
480
|
+
isFullMode,
|
|
481
|
+
isEditFlow,
|
|
482
|
+
cardVarMapped,
|
|
483
|
+
]);
|
|
484
|
+
|
|
485
|
+
const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
|
|
464
486
|
|
|
465
|
-
const RcsLabel = styled.div`
|
|
466
|
-
display: flex;
|
|
467
|
-
margin-top: 20px;
|
|
468
|
-
`;
|
|
469
487
|
const paramObj = params || {};
|
|
470
488
|
useEffect(() => {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}, [paramObj.id]);
|
|
489
|
+
const { id } = paramObj;
|
|
490
|
+
if (id && isFullMode) {
|
|
491
|
+
setSpin(true);
|
|
492
|
+
actions.getTemplateDetails(id, setSpin);
|
|
493
|
+
}
|
|
494
|
+
return () => {
|
|
495
|
+
actions.clearEditResponse();
|
|
496
|
+
};
|
|
497
|
+
}, [paramObj.id, isFullMode]);
|
|
480
498
|
|
|
481
499
|
useEffect(() => {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
500
|
+
if (!(isEditFlow || !isFullMode)) return;
|
|
501
|
+
|
|
502
|
+
const titleTokenCount = (templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length;
|
|
503
|
+
|
|
504
|
+
const initField = (targetString, setVarMap, slotOffset) => {
|
|
505
|
+
const arr = splitTemplateVarStringRcs(targetString);
|
|
506
|
+
if (!arr?.length) {
|
|
507
|
+
setVarMap({});
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const nextVarMap = {};
|
|
511
|
+
let varOrdinal = 0;
|
|
512
|
+
arr.forEach((elem, idx) => {
|
|
513
|
+
// RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
|
|
514
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
515
|
+
const id = `${elem}_${idx}`;
|
|
516
|
+
const varName = getVarNameFromToken(elem);
|
|
517
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
518
|
+
varOrdinal += 1;
|
|
519
|
+
const mappedValue = resolveCardVarMappedSlotValue(
|
|
520
|
+
cardVarMapped,
|
|
521
|
+
varName,
|
|
522
|
+
globalSlot,
|
|
523
|
+
isEditLike,
|
|
524
|
+
);
|
|
525
|
+
nextVarMap[id] = mappedValue;
|
|
490
526
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
// RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
|
|
495
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
496
|
-
const id = `${elem}_${idx}`;
|
|
497
|
-
const varName = getVarNameFromToken(elem);
|
|
498
|
-
const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
|
|
499
|
-
nextVarMap[id] = mappedValue;
|
|
500
|
-
if (mappedValue !== '') {
|
|
501
|
-
nextUpdated[idx] = mappedValue;
|
|
502
|
-
} else {
|
|
503
|
-
nextUpdated[idx] = elem;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
setVarMap(nextVarMap);
|
|
508
|
-
setUpdated(nextUpdated);
|
|
509
|
-
};
|
|
527
|
+
});
|
|
528
|
+
setVarMap(nextVarMap);
|
|
529
|
+
};
|
|
510
530
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
531
|
+
initField(templateTitle, setTitleVarMappedData, 0);
|
|
532
|
+
initField(templateDesc, setDescVarMappedData, titleTokenCount);
|
|
533
|
+
}, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
|
|
534
|
+
|
|
535
|
+
useEffect(() => {
|
|
536
|
+
if (!isEditFlow && isFullMode) {
|
|
517
537
|
setRcsVideoSrc({});
|
|
518
538
|
updateRcsImageSrc('');
|
|
519
539
|
setUpdateRcsImageSrc('');
|
|
520
540
|
updateRcsThumbnailSrc('');
|
|
521
541
|
setAssetList({});
|
|
522
|
-
|
|
523
|
-
|
|
542
|
+
}
|
|
543
|
+
}, [templateMediaType]);
|
|
524
544
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
545
|
+
/** Status on first card — same merged card as title/description/cardVarMapped (library may only set rcsContent at root). */
|
|
546
|
+
const templateStatusHelper = (cardContentFirst) => {
|
|
547
|
+
const raw =
|
|
548
|
+
cardContentFirst?.Status
|
|
549
|
+
?? cardContentFirst?.status
|
|
550
|
+
?? cardContentFirst?.approvalStatus
|
|
551
|
+
?? '';
|
|
552
|
+
const status = typeof raw === 'string' ? raw.trim() : String(raw);
|
|
553
|
+
const n = status.toLowerCase();
|
|
554
|
+
switch (n) {
|
|
555
|
+
case 'approved':
|
|
529
556
|
setTemplateStatus(RCS_STATUSES.approved);
|
|
530
557
|
break;
|
|
531
|
-
case
|
|
558
|
+
case 'pending':
|
|
532
559
|
setTemplateStatus(RCS_STATUSES.pending);
|
|
533
560
|
break;
|
|
534
|
-
case
|
|
561
|
+
case 'awaitingapproval':
|
|
535
562
|
setTemplateStatus(RCS_STATUSES.awaitingApproval);
|
|
536
563
|
break;
|
|
537
|
-
case
|
|
564
|
+
case 'unavailable':
|
|
538
565
|
setTemplateStatus(RCS_STATUSES.unavailable);
|
|
539
566
|
break;
|
|
540
|
-
case
|
|
567
|
+
case 'rejected':
|
|
541
568
|
setTemplateStatus(RCS_STATUSES.rejected);
|
|
542
569
|
break;
|
|
570
|
+
case 'created':
|
|
571
|
+
setTemplateStatus(RCS_STATUSES.created);
|
|
572
|
+
break;
|
|
543
573
|
default:
|
|
544
574
|
setTemplateStatus(status);
|
|
545
575
|
break;
|
|
@@ -550,7 +580,6 @@ export const Rcs = (props) => {
|
|
|
550
580
|
if (mediaType) {
|
|
551
581
|
setTemplateMediaType(mediaType);
|
|
552
582
|
}
|
|
553
|
-
const tempOrientation = cardSettings.cardOrientation;
|
|
554
583
|
const tempAlignment = cardSettings.mediaAlignment;
|
|
555
584
|
const tempHeight = mediaData.height;
|
|
556
585
|
|
|
@@ -593,44 +622,197 @@ export const Rcs = (props) => {
|
|
|
593
622
|
};
|
|
594
623
|
|
|
595
624
|
useEffect(() => {
|
|
596
|
-
const details =
|
|
625
|
+
const details = rcsHydrationDetails;
|
|
597
626
|
if (details && Object.keys(details).length > 0) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
627
|
+
// Library/campaign: match SMS fallback — read from versions… and from top-level rcsContent (getCreativesData / parent shape).
|
|
628
|
+
const cardFromVersions = get(
|
|
629
|
+
details,
|
|
630
|
+
'versions.base.content.RCS.rcsContent.cardContent[0]',
|
|
631
|
+
);
|
|
632
|
+
const cardFromTop = get(details, 'rcsContent.cardContent[0]');
|
|
633
|
+
const card0 = { ...(cardFromTop || {}), ...(cardFromVersions || {}) };
|
|
634
|
+
const cardVarMappedFromCardContent =
|
|
635
|
+
card0?.cardVarMapped != null && typeof card0.cardVarMapped === 'object'
|
|
636
|
+
? card0.cardVarMapped
|
|
637
|
+
: {};
|
|
638
|
+
const cardVarMappedFromRootMirror =
|
|
639
|
+
details?.rcsCardVarMapped != null && typeof details.rcsCardVarMapped === 'object'
|
|
640
|
+
? details.rcsCardVarMapped
|
|
641
|
+
: {};
|
|
642
|
+
// Root mirror from getCreativesData / getFormData — campaigns often preserve flat fields when
|
|
643
|
+
// nested versions.cardContent[0].cardVarMapped is dropped on reload.
|
|
644
|
+
const mergedCardVarMappedFromPayload = {
|
|
645
|
+
...cardVarMappedFromRootMirror,
|
|
646
|
+
...cardVarMappedFromCardContent,
|
|
647
|
+
};
|
|
648
|
+
const loadedTitleForMap = card0?.title != null ? String(card0.title) : '';
|
|
649
|
+
const loadedDescForMap = card0?.description != null ? String(card0.description) : '';
|
|
650
|
+
const hydratedCardVarPayloadSignature = `${loadedTitleForMap}\u0000${loadedDescForMap}\u0000${JSON.stringify(
|
|
651
|
+
Object.keys(mergedCardVarMappedFromPayload)
|
|
652
|
+
.sort()
|
|
653
|
+
.reduce((accumulator, mapKey) => {
|
|
654
|
+
accumulator[mapKey] = mergedCardVarMappedFromPayload[mapKey];
|
|
655
|
+
return accumulator;
|
|
656
|
+
}, {}),
|
|
657
|
+
)}`;
|
|
658
|
+
if (lastHydratedRcsCardVarSignatureRef.current !== hydratedCardVarPayloadSignature) {
|
|
659
|
+
lastHydratedRcsCardVarSignatureRef.current = hydratedCardVarPayloadSignature;
|
|
660
|
+
setRcsVarSegmentEditorRemountKey((previousKey) => previousKey + 1);
|
|
601
661
|
}
|
|
602
|
-
const
|
|
662
|
+
const tokenListForMap = [
|
|
663
|
+
...(loadedTitleForMap ? loadedTitleForMap.match(rcsVarRegex) ?? [] : []),
|
|
664
|
+
...(loadedDescForMap ? loadedDescForMap.match(rcsVarRegex) ?? [] : []),
|
|
665
|
+
];
|
|
666
|
+
const orderedTagNamesForMap = tokenListForMap.map((token) => getVarNameFromToken(token)).filter(Boolean);
|
|
667
|
+
// Full-mode library/API payloads need normalize for legacy slot shapes. Campaign round-trip from
|
|
668
|
+
// getFormData already stores TagList values as {{TagName}}; normalize can strip or remap them.
|
|
669
|
+
const cardVarMappedBeforeCoalesce = isFullMode
|
|
670
|
+
? normalizeCardVarMapped(mergedCardVarMappedFromPayload, orderedTagNamesForMap)
|
|
671
|
+
: { ...mergedCardVarMappedFromPayload };
|
|
672
|
+
const cardVarMappedAfterCoalesce = coalesceCardVarMappedToTemplate(
|
|
673
|
+
cardVarMappedBeforeCoalesce,
|
|
674
|
+
loadedTitleForMap,
|
|
675
|
+
loadedDescForMap,
|
|
676
|
+
rcsVarRegex,
|
|
677
|
+
);
|
|
678
|
+
const cardVarMappedAfterNumericSlotSync = !isFullMode
|
|
679
|
+
? syncCardVarMappedSemanticsFromSlots(
|
|
680
|
+
cardVarMappedAfterCoalesce,
|
|
681
|
+
loadedTitleForMap,
|
|
682
|
+
loadedDescForMap,
|
|
683
|
+
rcsVarRegex,
|
|
684
|
+
)
|
|
685
|
+
: cardVarMappedAfterCoalesce;
|
|
686
|
+
const hydratedCardVarMappedResult = { ...cardVarMappedAfterNumericSlotSync };
|
|
687
|
+
// Pre-populate variable/tag mappings while opening an existing template in edit flows
|
|
688
|
+
setCardVarMapped((previousVarMapState) => {
|
|
689
|
+
const previousVarMap = previousVarMapState ?? {};
|
|
690
|
+
if (previousVarMap === hydratedCardVarMappedResult) return previousVarMapState;
|
|
691
|
+
const previousVarMapKeys = Object.keys(previousVarMap);
|
|
692
|
+
const nextVarMapKeys = Object.keys(hydratedCardVarMappedResult);
|
|
693
|
+
if (previousVarMapKeys.length === nextVarMapKeys.length) {
|
|
694
|
+
const allSlotValuesMatchPrevious = previousVarMapKeys.every(
|
|
695
|
+
(key) => previousVarMap[key] === hydratedCardVarMappedResult[key],
|
|
696
|
+
);
|
|
697
|
+
if (allSlotValuesMatchPrevious) return previousVarMapState;
|
|
698
|
+
}
|
|
699
|
+
return hydratedCardVarMappedResult;
|
|
700
|
+
});
|
|
701
|
+
const mediaType =
|
|
702
|
+
card0.mediaType
|
|
703
|
+
|| get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
|
|
603
704
|
if (mediaType === RCS_MEDIA_TYPES.NONE) {
|
|
604
705
|
setTemplateType(contentType.text_message);
|
|
605
706
|
} else {
|
|
606
707
|
setTemplateType(contentType.rich_card);
|
|
607
708
|
}
|
|
608
709
|
setEditFlow(true);
|
|
609
|
-
setTemplateName(details
|
|
610
|
-
const loadedTitle =
|
|
611
|
-
const loadedDesc =
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
710
|
+
setTemplateName(details?.name || details?.creativeName || '');
|
|
711
|
+
const loadedTitle = loadedTitleForMap;
|
|
712
|
+
const loadedDesc = loadedDescForMap;
|
|
713
|
+
const { normalizedTitle, normalizedDesc } = normalizeLibraryLoadedTitleDesc({
|
|
714
|
+
loadedTitle,
|
|
715
|
+
loadedDesc,
|
|
716
|
+
isFullMode,
|
|
717
|
+
cardVarMappedAfterHydration: hydratedCardVarMappedResult,
|
|
718
|
+
rcsVarRegex,
|
|
719
|
+
});
|
|
615
720
|
setTemplateTitle(normalizedTitle);
|
|
616
721
|
setTemplateDesc(normalizedDesc);
|
|
617
|
-
setSuggestions(
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
722
|
+
setSuggestions(
|
|
723
|
+
Array.isArray(card0.suggestions)
|
|
724
|
+
? card0.suggestions
|
|
725
|
+
: get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []),
|
|
726
|
+
);
|
|
727
|
+
const cardForStatus = {
|
|
728
|
+
...card0,
|
|
729
|
+
Status:
|
|
730
|
+
card0.Status
|
|
731
|
+
?? card0.status
|
|
732
|
+
?? card0.approvalStatus
|
|
733
|
+
?? get(details, 'templateStatus')
|
|
734
|
+
?? get(details, 'approvalStatus')
|
|
735
|
+
?? get(details, 'creativeStatus')
|
|
736
|
+
?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
|
|
737
|
+
?? '',
|
|
738
|
+
};
|
|
739
|
+
templateStatusHelper(cardForStatus);
|
|
740
|
+
const mediaData =
|
|
741
|
+
card0.media != null && card0.media !== ''
|
|
742
|
+
? card0.media
|
|
743
|
+
: get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
|
|
744
|
+
const cardSettings =
|
|
745
|
+
get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '')
|
|
746
|
+
|| get(details, 'rcsContent.cardSettings', '');
|
|
621
747
|
setMediaData(mediaData, mediaType, cardSettings);
|
|
748
|
+
|
|
749
|
+
const smsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
|
|
750
|
+
const base = get(smsFallbackContent, 'versions.base', {});
|
|
751
|
+
const updatedEditor = base['updated-sms-editor'] ?? base['sms-editor'];
|
|
752
|
+
const smsEditor = base['sms-editor'];
|
|
753
|
+
const fromNested = Array.isArray(updatedEditor)
|
|
754
|
+
? updatedEditor.join('')
|
|
755
|
+
: (typeof updatedEditor === 'string' ? updatedEditor : (smsEditor || ''));
|
|
756
|
+
const fallbackMessage = smsFallbackContent.smsContent
|
|
757
|
+
|| smsFallbackContent.smsTemplateContent
|
|
758
|
+
|| smsFallbackContent.message
|
|
759
|
+
|| fromNested
|
|
760
|
+
|| '';
|
|
761
|
+
const varMappedFromPayload = smsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
|
|
762
|
+
const hasVarMapped = Object.keys(varMappedFromPayload).length > 0;
|
|
763
|
+
const hasFallbackPayload =
|
|
764
|
+
smsFallbackContent
|
|
765
|
+
&& Object.keys(smsFallbackContent).length > 0
|
|
766
|
+
&& (
|
|
767
|
+
!!smsFallbackContent.smsTemplateName
|
|
768
|
+
|| !!fallbackMessage
|
|
769
|
+
|| hasVarMapped
|
|
770
|
+
);
|
|
771
|
+
if (hasFallbackPayload) {
|
|
772
|
+
if (!fallbackMessage && !hasVarMapped && process.env.NODE_ENV !== 'production') {
|
|
773
|
+
console.warn('[RCS SMS Fallback] No message text found in API response. Inspect shape:', smsFallbackContent);
|
|
774
|
+
}
|
|
775
|
+
const unicodeFromApi =
|
|
776
|
+
typeof smsFallbackContent.unicodeValidity === 'boolean'
|
|
777
|
+
? smsFallbackContent.unicodeValidity
|
|
778
|
+
: (typeof base['unicode-validity'] === 'boolean' ? base['unicode-validity'] : true);
|
|
779
|
+
const nextSmsState = {
|
|
780
|
+
templateName: smsFallbackContent.smsTemplateName || '',
|
|
781
|
+
content: fallbackMessage,
|
|
782
|
+
templateContent: fallbackMessage,
|
|
783
|
+
unicodeValidity: unicodeFromApi,
|
|
784
|
+
...(hasVarMapped && { rcsSmsFallbackVarMapped: varMappedFromPayload }),
|
|
785
|
+
};
|
|
786
|
+
const hydrationKey = JSON.stringify({
|
|
787
|
+
creativeKey: details._id || details.name || details.creativeName || '',
|
|
788
|
+
templateName: nextSmsState.templateName,
|
|
789
|
+
content: nextSmsState.content,
|
|
790
|
+
unicodeValidity: nextSmsState.unicodeValidity,
|
|
791
|
+
varMapped: nextSmsState.rcsSmsFallbackVarMapped || {},
|
|
792
|
+
});
|
|
793
|
+
if (
|
|
794
|
+
isFullMode
|
|
795
|
+
|| lastSmsFallbackHydrationKeyRef.current !== hydrationKey
|
|
796
|
+
) {
|
|
797
|
+
lastSmsFallbackHydrationKeyRef.current = hydrationKey;
|
|
798
|
+
setSmsFallbackData(nextSmsState);
|
|
799
|
+
}
|
|
800
|
+
} else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
|
|
801
|
+
lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
|
|
802
|
+
setSmsFallbackData(null);
|
|
803
|
+
}
|
|
622
804
|
}
|
|
623
|
-
}, [
|
|
624
|
-
|
|
805
|
+
}, [rcsHydrationDetails, isFullMode]);
|
|
625
806
|
|
|
626
807
|
useEffect(() => {
|
|
627
808
|
if (templateType === contentType.text_message) {
|
|
628
809
|
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
629
|
-
|
|
630
|
-
|
|
810
|
+
// Full-mode create only: switching to plain text clears draft title/media. Never clear when
|
|
811
|
+
// hydrating library/edit (would wipe templateData after load) — regression seen after SMS fallback work.
|
|
631
812
|
if (!isEditFlow && isFullMode) {
|
|
813
|
+
setTemplateTitle('');
|
|
814
|
+
setTemplateTitleError('');
|
|
632
815
|
setUpdateRcsImageSrc('');
|
|
633
|
-
setUpdateRcsVideoSrc({});
|
|
634
816
|
setRcsVideoSrc({});
|
|
635
817
|
setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
636
818
|
}
|
|
@@ -657,7 +839,8 @@ export const Rcs = (props) => {
|
|
|
657
839
|
if (!showDltContainer) {
|
|
658
840
|
const { type, module } = location.query || {};
|
|
659
841
|
const isEmbedded = type === EMBEDDED;
|
|
660
|
-
|
|
842
|
+
// Match TagList initial fetch (getTagsforContext('Outbound') → context "outbound") so we do not request a different context than the two TagList headers.
|
|
843
|
+
const context = isEmbedded ? module : 'outbound';
|
|
661
844
|
const embedded = isEmbedded ? type : FULL;
|
|
662
845
|
const query = {
|
|
663
846
|
layout: SMS,
|
|
@@ -668,9 +851,9 @@ export const Rcs = (props) => {
|
|
|
668
851
|
if (getDefaultTags) {
|
|
669
852
|
query.context = getDefaultTags;
|
|
670
853
|
}
|
|
671
|
-
|
|
854
|
+
fetchTagSchemaIfNewQuery(query);
|
|
672
855
|
}
|
|
673
|
-
}, [showDltContainer]);
|
|
856
|
+
}, [showDltContainer, fetchTagSchemaIfNewQuery]);
|
|
674
857
|
|
|
675
858
|
useEffect(() => {
|
|
676
859
|
let tag = get(metaEntities, `tags.standard`, []);
|
|
@@ -693,39 +876,72 @@ export const Rcs = (props) => {
|
|
|
693
876
|
context,
|
|
694
877
|
embedded,
|
|
695
878
|
};
|
|
696
|
-
|
|
879
|
+
if (getDefaultTags) {
|
|
880
|
+
query.context = getDefaultTags;
|
|
881
|
+
}
|
|
882
|
+
fetchTagSchemaIfNewQuery(query);
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
|
|
886
|
+
if (!templateStr || !numericVarName || !tagName) return templateStr;
|
|
887
|
+
const re = buildRcsNumericMustachePlaceholderRegex(numericVarName);
|
|
888
|
+
return templateStr.replace(re, `{{${tagName}}}`);
|
|
697
889
|
};
|
|
698
890
|
|
|
699
|
-
const onTagSelect = (data, areaId) => {
|
|
891
|
+
const onTagSelect = (data, areaId, field) => {
|
|
700
892
|
if (!areaId) return;
|
|
701
893
|
const sep = areaId.lastIndexOf('_');
|
|
702
894
|
if (sep === -1) return;
|
|
703
|
-
const
|
|
704
|
-
if (isNaN(
|
|
895
|
+
const slotSuffix = areaId.slice(sep + 1);
|
|
896
|
+
if (slotSuffix === '' || isNaN(Number(slotSuffix))) return;
|
|
705
897
|
const token = areaId.slice(0, sep);
|
|
706
898
|
const variableName = getVarNameFromToken(token);
|
|
707
899
|
if (!variableName) return;
|
|
900
|
+
const isNumericSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(variableName));
|
|
901
|
+
const fieldStr = field === RCS_TAG_AREA_FIELD_TITLE ? templateTitle : templateDesc;
|
|
902
|
+
const fieldType = field === RCS_TAG_AREA_FIELD_TITLE ? TITLE_TEXT : MESSAGE_TEXT;
|
|
903
|
+
const globalSlotForArea = getGlobalSlotIndexForRcsFieldId(areaId, fieldStr, fieldType);
|
|
904
|
+
|
|
708
905
|
setCardVarMapped((prev) => {
|
|
709
906
|
const base = (prev?.[variableName] ?? '').toString();
|
|
710
907
|
const nextVal = `${base}{{${data}}}`;
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
[variableName]
|
|
714
|
-
|
|
908
|
+
const next = { ...(prev || {}) };
|
|
909
|
+
if (isNumericSlot) {
|
|
910
|
+
delete next[variableName];
|
|
911
|
+
next[data] = nextVal;
|
|
912
|
+
} else {
|
|
913
|
+
next[variableName] = nextVal;
|
|
914
|
+
// resolveCardVarMappedSlotValue prefers numeric slot keys ("1","2",…) over semantic names;
|
|
915
|
+
// hydration may set "1": ''. Use global slot index — suffix is segment index (includes static text), not var ordinal.
|
|
916
|
+
if (globalSlotForArea !== null && globalSlotForArea !== undefined) {
|
|
917
|
+
next[String(globalSlotForArea + 1)] = nextVal;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return next;
|
|
715
921
|
});
|
|
716
|
-
};
|
|
717
|
-
|
|
718
|
-
const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
|
|
719
|
-
|
|
720
|
-
const onDescTagSelect = (data) => onTagSelect(data, descTextAreaId);
|
|
721
922
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
923
|
+
if (isNumericSlot && (field === RCS_TAG_AREA_FIELD_TITLE || field === RCS_TAG_AREA_FIELD_DESC)) {
|
|
924
|
+
if (field === RCS_TAG_AREA_FIELD_TITLE) {
|
|
925
|
+
setTemplateTitle((prev) => {
|
|
926
|
+
const nextStr = replaceNumericPlaceholderWithTagInTemplate(prev || '', variableName, data);
|
|
927
|
+
if (nextStr === prev) return prev;
|
|
928
|
+
setTemplateTitleError(variableErrorHandling(nextStr));
|
|
929
|
+
return nextStr;
|
|
930
|
+
});
|
|
931
|
+
} else {
|
|
932
|
+
setTemplateDesc((prev) => {
|
|
933
|
+
const nextStr = replaceNumericPlaceholderWithTagInTemplate(prev || '', variableName, data);
|
|
934
|
+
if (nextStr === prev) return prev;
|
|
935
|
+
setTemplateDescError(variableErrorHandling(nextStr));
|
|
936
|
+
return nextStr;
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
}
|
|
727
940
|
};
|
|
728
941
|
|
|
942
|
+
const onTitleTagSelect = (tagName) => onTagSelect(tagName, titleTextAreaId, RCS_TAG_AREA_FIELD_TITLE);
|
|
943
|
+
|
|
944
|
+
const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
|
|
729
945
|
|
|
730
946
|
//removing optout tag for rcs
|
|
731
947
|
const getRcsTags = () => {
|
|
@@ -738,15 +954,14 @@ export const Rcs = (props) => {
|
|
|
738
954
|
};
|
|
739
955
|
// tag Code end
|
|
740
956
|
|
|
741
|
-
const renderLabel = (value,
|
|
742
|
-
const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
|
|
957
|
+
const renderLabel = (value, desc) => {
|
|
743
958
|
return (
|
|
744
959
|
<>
|
|
745
|
-
<
|
|
960
|
+
<div className="rcs-form-section-heading">
|
|
746
961
|
<CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
|
|
747
|
-
</
|
|
962
|
+
</div>
|
|
748
963
|
{desc && (
|
|
749
|
-
<CapLabel type="label3"
|
|
964
|
+
<CapLabel type="label3" className="rcs-form-field-caption">
|
|
750
965
|
{formatMessage(messages[desc])}
|
|
751
966
|
</CapLabel>
|
|
752
967
|
)}
|
|
@@ -832,64 +1047,6 @@ export const Rcs = (props) => {
|
|
|
832
1047
|
setTemplateDescError(error);
|
|
833
1048
|
};
|
|
834
1049
|
|
|
835
|
-
|
|
836
|
-
const templateDescErrorHandler = (value) => {
|
|
837
|
-
let errorMessage = false;
|
|
838
|
-
const { unsupportedTags, isBraceError } = validateTags({
|
|
839
|
-
content: value,
|
|
840
|
-
tagsParam: tags,
|
|
841
|
-
injectedTagsParams: injectedTags,
|
|
842
|
-
location,
|
|
843
|
-
tagModule: getDefaultTags,
|
|
844
|
-
isFullMode,
|
|
845
|
-
}) || {};
|
|
846
|
-
|
|
847
|
-
const maxLength = templateType === contentType.text_message
|
|
848
|
-
? RCS_TEXT_MESSAGE_MAX_LENGTH
|
|
849
|
-
: RCS_RICH_CARD_MAX_LENGTH;
|
|
850
|
-
|
|
851
|
-
if (value === '' && isMediaTypeText) {
|
|
852
|
-
errorMessage = formatMessage(messages.emptyTemplateDescErrorMessage);
|
|
853
|
-
} else if (value?.length > maxLength) {
|
|
854
|
-
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
if (isBraceError) {
|
|
858
|
-
errorMessage = formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
859
|
-
}
|
|
860
|
-
return errorMessage;
|
|
861
|
-
};
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
const onFallbackMessageChange = ({ target: { value } }) => {
|
|
865
|
-
const error = fallbackMessageErrorHandler(value);
|
|
866
|
-
setFallbackMessage(value);
|
|
867
|
-
setFallbackMessageError(error);
|
|
868
|
-
};
|
|
869
|
-
|
|
870
|
-
const fallbackMessageErrorHandler = (value) => {
|
|
871
|
-
let errorMessage = false;
|
|
872
|
-
const { unsupportedTags } = validateTags({
|
|
873
|
-
content: value,
|
|
874
|
-
tagsParam: tags,
|
|
875
|
-
injectedTagsParams: injectedTags,
|
|
876
|
-
location,
|
|
877
|
-
tagModule: getDefaultTags,
|
|
878
|
-
isFullMode,
|
|
879
|
-
}) || {};
|
|
880
|
-
if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
|
|
881
|
-
errorMessage = formatMessage(messages.fallbackMsgLenError);
|
|
882
|
-
} else if (unsupportedTags?.length > 0) {
|
|
883
|
-
errorMessage = formatMessage(
|
|
884
|
-
globalMessages.unsupportedTagsValidationError,
|
|
885
|
-
{
|
|
886
|
-
unsupportedTags,
|
|
887
|
-
},
|
|
888
|
-
);
|
|
889
|
-
}
|
|
890
|
-
return errorMessage;
|
|
891
|
-
};
|
|
892
|
-
|
|
893
1050
|
// Check for forbidden characters: square brackets [] and single curly braces {}
|
|
894
1051
|
const forbiddenCharactersValidation = (value) => {
|
|
895
1052
|
if (!value) return false;
|
|
@@ -940,39 +1097,12 @@ export const Rcs = (props) => {
|
|
|
940
1097
|
}
|
|
941
1098
|
return false;
|
|
942
1099
|
};
|
|
943
|
-
|
|
944
|
-
const templateHeaderErrorHandler = (value) => {
|
|
945
|
-
let errorMessage = false;
|
|
946
|
-
if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
|
|
947
|
-
errorMessage = formatMessage(messages.templateHeaderLengthError);
|
|
948
|
-
} else {
|
|
949
|
-
errorMessage = variableErrorHandling(value);
|
|
950
|
-
}
|
|
951
|
-
return errorMessage;
|
|
952
|
-
};
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const templateMessageErrorHandler = (value) => {
|
|
956
|
-
let errorMessage = false;
|
|
957
|
-
if (value === '') {
|
|
958
|
-
errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
|
|
959
|
-
} else if (
|
|
960
|
-
value?.length
|
|
961
|
-
> TEMPLATE_MESSAGE_MAX_LENGTH
|
|
962
|
-
) {
|
|
963
|
-
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
964
|
-
} else {
|
|
965
|
-
errorMessage = variableErrorHandling(value);
|
|
966
|
-
}
|
|
967
|
-
return errorMessage;
|
|
968
|
-
};
|
|
969
|
-
|
|
970
1100
|
|
|
971
1101
|
const onMessageAddVar = () => {
|
|
972
|
-
onAddVar(
|
|
1102
|
+
onAddVar(templateDesc);
|
|
973
1103
|
};
|
|
974
1104
|
|
|
975
|
-
const onAddVar = (
|
|
1105
|
+
const onAddVar = (messageContent) => {
|
|
976
1106
|
// Always append the next variable at the end, like WhatsApp
|
|
977
1107
|
const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
|
|
978
1108
|
const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
|
|
@@ -1012,66 +1142,6 @@ const onTitleAddVar = () => {
|
|
|
1012
1142
|
setTemplateTitleError(error);
|
|
1013
1143
|
};
|
|
1014
1144
|
|
|
1015
|
-
|
|
1016
|
-
const splitTemplateVarString = (str) => {
|
|
1017
|
-
if (!str) return [];
|
|
1018
|
-
const validVarArr = str.match(rcsVarRegex) || [];
|
|
1019
|
-
const templateVarArray = [];
|
|
1020
|
-
let content = str;
|
|
1021
|
-
while (content?.length !== 0) {
|
|
1022
|
-
const index = content.indexOf(validVarArr?.[0]);
|
|
1023
|
-
if (index !== -1) {
|
|
1024
|
-
templateVarArray.push(content.substring(0, index));
|
|
1025
|
-
templateVarArray.push(validVarArr?.[0]);
|
|
1026
|
-
content = content.substring(index + validVarArr?.[0]?.length, content?.length);
|
|
1027
|
-
validVarArr?.shift();
|
|
1028
|
-
} else {
|
|
1029
|
-
templateVarArray.push(content);
|
|
1030
|
-
break;
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
return templateVarArray.filter(Boolean);
|
|
1034
|
-
};
|
|
1035
|
-
|
|
1036
|
-
const textAreaValue = (idValue, type) => {
|
|
1037
|
-
if (idValue >= 0) {
|
|
1038
|
-
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
1039
|
-
const templateArr = splitTemplateVarString(templateStr);
|
|
1040
|
-
const token = templateArr?.[idValue] || "";
|
|
1041
|
-
if (token && rcsVarTestRegex.test(token)) {
|
|
1042
|
-
const varName = getVarNameFromToken(token);
|
|
1043
|
-
return (cardVarMapped?.[varName] ?? '').toString();
|
|
1044
|
-
}
|
|
1045
|
-
return "";
|
|
1046
|
-
}
|
|
1047
|
-
return "";
|
|
1048
|
-
};
|
|
1049
|
-
|
|
1050
|
-
const textAreaValueChange = (e, type) => {
|
|
1051
|
-
const value = e?.target?.value ?? '';
|
|
1052
|
-
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1053
|
-
if (!id) return;
|
|
1054
|
-
const sep = id.lastIndexOf('_');
|
|
1055
|
-
if (sep === -1) return;
|
|
1056
|
-
const isInvalidValue = value?.trim() === "";
|
|
1057
|
-
const token = id.slice(0, sep);
|
|
1058
|
-
const variableName = getVarNameFromToken(token);
|
|
1059
|
-
|
|
1060
|
-
if (variableName) {
|
|
1061
|
-
setCardVarMapped((prev) => ({
|
|
1062
|
-
...prev,
|
|
1063
|
-
[variableName]: isInvalidValue ? "" : value,
|
|
1064
|
-
}));
|
|
1065
|
-
}
|
|
1066
|
-
};
|
|
1067
|
-
|
|
1068
|
-
const setTextAreaId = (e, type) => {
|
|
1069
|
-
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1070
|
-
if (!id) return;
|
|
1071
|
-
if (type === TITLE_TEXT) setTitleTextAreaId(id);
|
|
1072
|
-
else setDescTextAreaId(id);
|
|
1073
|
-
};
|
|
1074
|
-
|
|
1075
1145
|
const renderButtonComponent = () => {
|
|
1076
1146
|
return (
|
|
1077
1147
|
<>
|
|
@@ -1102,39 +1172,56 @@ const splitTemplateVarString = (str) => {
|
|
|
1102
1172
|
);
|
|
1103
1173
|
};
|
|
1104
1174
|
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1175
|
+
const getRcsValueMap = (fieldTemplateString, fieldType) => {
|
|
1176
|
+
if (!fieldTemplateString) return {};
|
|
1177
|
+
const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
|
|
1178
|
+
const slotOffset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
|
|
1179
|
+
const templateSegments = splitTemplateVarStringRcs(fieldTemplateString);
|
|
1180
|
+
const segmentIdToResolvedValue = {};
|
|
1181
|
+
let varOrdinal = 0;
|
|
1182
|
+
templateSegments.forEach((segmentToken, segmentIndexInField) => {
|
|
1183
|
+
if (rcsVarTestRegex.test(segmentToken)) {
|
|
1184
|
+
const varSegmentCompositeId = `${segmentToken}_${segmentIndexInField}`;
|
|
1185
|
+
const varName = getVarNameFromToken(segmentToken);
|
|
1186
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
1187
|
+
varOrdinal += 1;
|
|
1188
|
+
segmentIdToResolvedValue[varSegmentCompositeId] = resolveCardVarMappedSlotValue(
|
|
1189
|
+
cardVarMapped,
|
|
1190
|
+
varName,
|
|
1191
|
+
globalSlot,
|
|
1192
|
+
isEditLike,
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
return segmentIdToResolvedValue;
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
const titleVarSegmentValueMapById = useMemo(
|
|
1200
|
+
() => getRcsValueMap(templateTitle, TITLE_TEXT),
|
|
1201
|
+
[templateTitle, cardVarMapped, isEditFlow, isFullMode],
|
|
1202
|
+
);
|
|
1203
|
+
const descriptionVarSegmentValueMapById = useMemo(
|
|
1204
|
+
() => getRcsValueMap(templateDesc, MESSAGE_TEXT),
|
|
1205
|
+
[templateDesc, templateTitle, cardVarMapped, isEditFlow, isFullMode],
|
|
1206
|
+
);
|
|
1207
|
+
|
|
1208
|
+
const handleRcsVarChange = (id, value, type) => {
|
|
1209
|
+
const sep = id.lastIndexOf('_');
|
|
1210
|
+
if (sep === -1) return;
|
|
1211
|
+
const token = id.slice(0, sep);
|
|
1212
|
+
const variableName = getVarNameFromToken(token);
|
|
1213
|
+
if (variableName === undefined || variableName === null || variableName === '') return;
|
|
1214
|
+
const isInvalidValue = value?.trim() === '';
|
|
1215
|
+
const coercedSlotValue = isInvalidValue ? '' : value;
|
|
1216
|
+
const fieldStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
1217
|
+
const globalSlot = getGlobalSlotIndexForRcsFieldId(id, fieldStr, type);
|
|
1218
|
+
setCardVarMapped((previousVarMap) => {
|
|
1219
|
+
const nextVarMap = { ...previousVarMap, [variableName]: coercedSlotValue };
|
|
1220
|
+
if (globalSlot !== null && globalSlot !== undefined) {
|
|
1221
|
+
nextVarMap[String(globalSlot + 1)] = coercedSlotValue;
|
|
1222
|
+
}
|
|
1223
|
+
return nextVarMap;
|
|
1224
|
+
});
|
|
1138
1225
|
};
|
|
1139
1226
|
|
|
1140
1227
|
const renderTextComponent = () => {
|
|
@@ -1178,10 +1265,19 @@ const splitTemplateVarString = (str) => {
|
|
|
1178
1265
|
</>
|
|
1179
1266
|
}
|
|
1180
1267
|
/>
|
|
1181
|
-
<div className="rcs_text_area_wrapper">
|
|
1182
1268
|
{(isEditFlow || !isFullMode) ? (
|
|
1183
|
-
|
|
1269
|
+
<VarSegmentMessageEditor
|
|
1270
|
+
key={`rcs-title-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
1271
|
+
templateString={templateTitle}
|
|
1272
|
+
valueMap={titleVarSegmentValueMapById}
|
|
1273
|
+
onChange={(id, value) => handleRcsVarChange(id, value, TITLE_TEXT)}
|
|
1274
|
+
onFocus={(id) => setTitleTextAreaId(id)}
|
|
1275
|
+
varRegex={rcsVarRegex}
|
|
1276
|
+
placeholderPrefix=""
|
|
1277
|
+
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
1278
|
+
/>
|
|
1184
1279
|
) : (
|
|
1280
|
+
<div className="rcs_text_area_wrapper">
|
|
1185
1281
|
<CapInput
|
|
1186
1282
|
className={`rcs-template-title-input ${
|
|
1187
1283
|
!isTemplateApproved ? "rcs-edit-disabled" : ""
|
|
@@ -1194,8 +1290,8 @@ const splitTemplateVarString = (str) => {
|
|
|
1194
1290
|
errorMessage={templateTitleError}
|
|
1195
1291
|
disabled={isEditFlow || !isFullMode}
|
|
1196
1292
|
/>
|
|
1293
|
+
</div>
|
|
1197
1294
|
)}
|
|
1198
|
-
</div>
|
|
1199
1295
|
{(isEditFlow || !isFullMode) && templateTitleError && (
|
|
1200
1296
|
<CapError className="rcs-template-title-error">
|
|
1201
1297
|
{templateTitleError}
|
|
@@ -1243,7 +1339,18 @@ const splitTemplateVarString = (str) => {
|
|
|
1243
1339
|
<CapRow className="rcs-create-template-message-input">
|
|
1244
1340
|
<div className="rcs_text_area_wrapper">
|
|
1245
1341
|
{(isEditFlow || !isFullMode)
|
|
1246
|
-
?
|
|
1342
|
+
? (
|
|
1343
|
+
<VarSegmentMessageEditor
|
|
1344
|
+
key={`rcs-desc-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
1345
|
+
templateString={templateDesc}
|
|
1346
|
+
valueMap={descriptionVarSegmentValueMapById}
|
|
1347
|
+
onChange={(id, value) => handleRcsVarChange(id, value, MESSAGE_TEXT)}
|
|
1348
|
+
onFocus={(id) => setDescTextAreaId(id)}
|
|
1349
|
+
varRegex={rcsVarRegex}
|
|
1350
|
+
placeholderPrefix=""
|
|
1351
|
+
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
1352
|
+
/>
|
|
1353
|
+
)
|
|
1247
1354
|
: (
|
|
1248
1355
|
<>
|
|
1249
1356
|
<TextArea
|
|
@@ -1265,7 +1372,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1265
1372
|
data-testid="rcs_text_area"
|
|
1266
1373
|
disabled={(isEditFlow || !isFullMode)}
|
|
1267
1374
|
/>
|
|
1268
|
-
{!
|
|
1375
|
+
{!aiContentBotDisabled && !isEditFlow && (
|
|
1269
1376
|
<CapAskAira.ContentGenerationBot
|
|
1270
1377
|
text={templateDesc || ""}
|
|
1271
1378
|
setText={(text) => onTemplateDescChange({ target: { value: text } })}
|
|
@@ -1287,7 +1394,9 @@ const splitTemplateVarString = (str) => {
|
|
|
1287
1394
|
{templateDescError}
|
|
1288
1395
|
</CapError>
|
|
1289
1396
|
)}
|
|
1290
|
-
{
|
|
1397
|
+
{(isEditFlow || !isFullMode)
|
|
1398
|
+
? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
|
|
1399
|
+
: (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
|
|
1291
1400
|
{!isFullMode && hasTag() && (
|
|
1292
1401
|
<CapAlert
|
|
1293
1402
|
message={
|
|
@@ -1307,18 +1416,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1307
1416
|
);
|
|
1308
1417
|
};
|
|
1309
1418
|
|
|
1310
|
-
|
|
1311
|
-
const fallbackSmsLength = () => (
|
|
1312
|
-
<CapLabel type="label1" className="fallback-sms-length">
|
|
1313
|
-
{formatMessage(messages.totalCharacters, {
|
|
1314
|
-
smsCount: Math.ceil(
|
|
1315
|
-
fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
1316
|
-
),
|
|
1317
|
-
number: fallbackMessage?.length,
|
|
1318
|
-
})}
|
|
1319
|
-
</CapLabel>
|
|
1320
|
-
);
|
|
1321
|
-
|
|
1322
1419
|
// Get character count for title (rich card only)
|
|
1323
1420
|
const getTitleCharacterCount = () => {
|
|
1324
1421
|
if (templateType === contentType.text_message) return 0;
|
|
@@ -1378,7 +1475,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1378
1475
|
const hasTag = () => {
|
|
1379
1476
|
// Check cardVarMapped values for tags
|
|
1380
1477
|
if (cardVarMapped && Object.keys(cardVarMapped).length > 0) {
|
|
1381
|
-
const hasTagInMapped = Object.values(cardVarMapped).some(value =>
|
|
1478
|
+
const hasTagInMapped = Object.values(cardVarMapped).some((value) =>
|
|
1382
1479
|
isTagIncluded(value)
|
|
1383
1480
|
);
|
|
1384
1481
|
if (hasTagInMapped) return true;
|
|
@@ -1396,14 +1493,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1396
1493
|
return false;
|
|
1397
1494
|
};
|
|
1398
1495
|
|
|
1399
|
-
//adding creative dlt fallback sms handlers
|
|
1400
|
-
const addDltMsgHandler = () => {
|
|
1401
|
-
setShowDltContainer(true);
|
|
1402
|
-
setDltMode(RCS_DLT_MODE.TEMPLATES);
|
|
1403
|
-
setDltEditData({});
|
|
1404
|
-
setFallbackMessage('');
|
|
1405
|
-
};
|
|
1406
|
-
|
|
1407
1496
|
const closeDltContainerHandler = () => {
|
|
1408
1497
|
setShowDltContainer(false);
|
|
1409
1498
|
setDltMode('');
|
|
@@ -1426,68 +1515,17 @@ const splitTemplateVarString = (str) => {
|
|
|
1426
1515
|
const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
|
|
1427
1516
|
'',
|
|
1428
1517
|
);
|
|
1518
|
+
const templateNameFromDlt = get(dltEditData, 'name', '')
|
|
1519
|
+
|| get(tempData, 'versions.base.name', '')
|
|
1520
|
+
|| '';
|
|
1429
1521
|
closeDltContainerHandler();
|
|
1430
1522
|
setDltEditData(tempData);
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
closeDltContainerHandler();
|
|
1438
|
-
setDltEditData({});
|
|
1439
|
-
setFallbackMessage('');
|
|
1440
|
-
setFallbackMessageError(false);
|
|
1441
|
-
setShowDltCard(false);
|
|
1442
|
-
};
|
|
1443
|
-
|
|
1444
|
-
const dltFallbackListingPreviewhandler = (data) => {
|
|
1445
|
-
const {
|
|
1446
|
-
'updated-sms-editor': updatedSmsEditor = [],
|
|
1447
|
-
'sms-editor': smsEditor = '',
|
|
1448
|
-
} = data.versions.base || {};
|
|
1449
|
-
setFallbackPreviewmode(true);
|
|
1450
|
-
setDltPreviewData(
|
|
1451
|
-
updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
|
|
1452
|
-
);
|
|
1453
|
-
};
|
|
1454
|
-
|
|
1455
|
-
const getDltContentCardList = (content, channel) => {
|
|
1456
|
-
const extra = [
|
|
1457
|
-
<CapIcon
|
|
1458
|
-
type="edit"
|
|
1459
|
-
style={{ marginRight: '8px' }}
|
|
1460
|
-
onClick={() => rcsDltEditSelectHandler(dltEditData)}
|
|
1461
|
-
/>,
|
|
1462
|
-
<CapDropdown
|
|
1463
|
-
overlay={(
|
|
1464
|
-
<CapMenu>
|
|
1465
|
-
<>
|
|
1466
|
-
<CapMenu.Item
|
|
1467
|
-
className="ant-dropdown-menu-item"
|
|
1468
|
-
onClick={() => setFallbackPreviewmode(true)}
|
|
1469
|
-
>
|
|
1470
|
-
{formatMessage(globalMessages.preview)}
|
|
1471
|
-
</CapMenu.Item>
|
|
1472
|
-
<CapMenu.Item
|
|
1473
|
-
className="ant-dropdown-menu-item"
|
|
1474
|
-
onClick={rcsDltCardDeleteHandler}
|
|
1475
|
-
>
|
|
1476
|
-
{formatMessage(globalMessages.delete)}
|
|
1477
|
-
</CapMenu.Item>
|
|
1478
|
-
</>
|
|
1479
|
-
</CapMenu>
|
|
1480
|
-
)}
|
|
1481
|
-
>
|
|
1482
|
-
<CapIcon type="more" />
|
|
1483
|
-
</CapDropdown>,
|
|
1484
|
-
];
|
|
1485
|
-
return {
|
|
1486
|
-
title: channel,
|
|
1487
|
-
content,
|
|
1488
|
-
cardType: channel,
|
|
1489
|
-
extra,
|
|
1490
|
-
};
|
|
1523
|
+
const unicodeFromDlt = get(tempData, 'versions.base.unicode-validity');
|
|
1524
|
+
setSmsFallbackData({
|
|
1525
|
+
templateName: templateNameFromDlt,
|
|
1526
|
+
content: fallMsg,
|
|
1527
|
+
...(typeof unicodeFromDlt === 'boolean' ? { unicodeValidity: unicodeFromDlt } : {}),
|
|
1528
|
+
});
|
|
1491
1529
|
};
|
|
1492
1530
|
|
|
1493
1531
|
const getDltSlideBoxContent = () => {
|
|
@@ -1514,7 +1552,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1514
1552
|
isFullMode={isFullMode}
|
|
1515
1553
|
isDltFromRcs
|
|
1516
1554
|
onSelectTemplate={rcsDltEditSelectHandler}
|
|
1517
|
-
handlePeviewTemplate={dltFallbackListingPreviewhandler}
|
|
1518
1555
|
/>
|
|
1519
1556
|
);
|
|
1520
1557
|
} else if (dltMode === RCS_DLT_MODE.EDIT) {
|
|
@@ -1535,147 +1572,32 @@ const splitTemplateVarString = (str) => {
|
|
|
1535
1572
|
return { dltHeader, dltContent };
|
|
1536
1573
|
};
|
|
1537
1574
|
|
|
1538
|
-
const renderFallBackSmsComponent = () =>
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
// style: { marginLeft: '4px', marginTop: '3px' },
|
|
1563
|
-
// }}
|
|
1564
|
-
// title={formatMessage(messages.fallbackToolTip)}
|
|
1565
|
-
// />
|
|
1566
|
-
// </CapRow>
|
|
1567
|
-
// )}
|
|
1568
|
-
// description={formatMessage(messages.fallbackDesc)}
|
|
1569
|
-
// suffix={
|
|
1570
|
-
// isDltEnabled ? null : (
|
|
1571
|
-
// <CapButton
|
|
1572
|
-
// type="flat"
|
|
1573
|
-
// className="fallback-preview-btn"
|
|
1574
|
-
// prefix={<CapIcon type="eye" />}
|
|
1575
|
-
// style={{ color: CAP_SECONDARY.base }}
|
|
1576
|
-
// onClick={() => setFallbackPreviewmode(true)}
|
|
1577
|
-
// disabled={fallbackMessage === '' || fallbackMessageError}
|
|
1578
|
-
// >
|
|
1579
|
-
// {formatMessage(globalMessages.preview)}
|
|
1580
|
-
// </CapButton>
|
|
1581
|
-
// )
|
|
1582
|
-
// }
|
|
1583
|
-
// />
|
|
1584
|
-
// </CapRow>,
|
|
1585
|
-
// );
|
|
1586
|
-
|
|
1587
|
-
//dlt is enabled, and dlt content is not yet added, show button to add dlt creative
|
|
1588
|
-
// showAddCreativeBtnForDlt
|
|
1589
|
-
// && contentArr.push(
|
|
1590
|
-
// <CapCard className="rcs-dlt-fallback-card">
|
|
1591
|
-
// <CapRow type="flex" justify="center" align="middle">
|
|
1592
|
-
// <CapColumn span={10}>
|
|
1593
|
-
// <CapImage src={addCreativesIcon} />
|
|
1594
|
-
// </CapColumn>
|
|
1595
|
-
// <CapColumn span={14}>
|
|
1596
|
-
// <CapButton
|
|
1597
|
-
// className="add-dlt-btn"
|
|
1598
|
-
// type="secondary"
|
|
1599
|
-
// onClick={addDltMsgHandler}
|
|
1600
|
-
// >
|
|
1601
|
-
// {formatMessage(messages.addSmsCreative)}
|
|
1602
|
-
// </CapButton>
|
|
1603
|
-
// </CapColumn>
|
|
1604
|
-
// </CapRow>
|
|
1605
|
-
// </CapCard>,
|
|
1606
|
-
// );
|
|
1607
|
-
|
|
1608
|
-
// //dlt is enabled and dlt content is added, show it in a card
|
|
1609
|
-
// showCardForDlt
|
|
1610
|
-
// && contentArr.push(
|
|
1611
|
-
// <CapCustomCardList
|
|
1612
|
-
// cardList={[getDltContentCardList(fallbackMessage, SMS)]}
|
|
1613
|
-
// className="rcs-dlt-card"
|
|
1614
|
-
// />,
|
|
1615
|
-
// fallbackMessageError && (
|
|
1616
|
-
// <CapError className="rcs-fallback-len-error">
|
|
1617
|
-
// {formatMessage(messages.fallbackMsgLenError)}
|
|
1618
|
-
// </CapError>
|
|
1619
|
-
// ),
|
|
1620
|
-
// );
|
|
1621
|
-
|
|
1622
|
-
// //dlt is not enabled, show non dlt text area
|
|
1623
|
-
// showNonDltFallbackComp
|
|
1624
|
-
// && contentArr.push(
|
|
1625
|
-
// <>
|
|
1626
|
-
// <CapRow>
|
|
1627
|
-
// <CapHeader
|
|
1628
|
-
// title={(
|
|
1629
|
-
// <CapHeading type="h4">
|
|
1630
|
-
// {formatMessage(messages.fallbackTextAreaLabel)}
|
|
1631
|
-
// </CapHeading>
|
|
1632
|
-
// )}
|
|
1633
|
-
// suffix={(
|
|
1634
|
-
// <TagList
|
|
1635
|
-
// label={formatMessage(globalMessages.addLabels)}
|
|
1636
|
-
// onTagSelect={onTagSelectFallback}
|
|
1637
|
-
// location={location}
|
|
1638
|
-
// tags={tags || []}
|
|
1639
|
-
// onContextChange={handleOnTagsContextChange}
|
|
1640
|
-
// injectedTags={injectedTags || {}}
|
|
1641
|
-
// selectedOfferDetails={selectedOfferDetails}
|
|
1642
|
-
// />
|
|
1643
|
-
// )}
|
|
1644
|
-
// />
|
|
1645
|
-
// </CapRow>
|
|
1646
|
-
// <div className="rcs_fallback_msg_textarea_wrapper">
|
|
1647
|
-
// <TextArea
|
|
1648
|
-
// id="rcs_fallback_message_textarea"
|
|
1649
|
-
// autosize={{ minRows: 3, maxRows: 5 }}
|
|
1650
|
-
// placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
|
|
1651
|
-
// onChange={onFallbackMessageChange}
|
|
1652
|
-
// errorMessage={fallbackMessageError}
|
|
1653
|
-
// value={fallbackMessage || ""}
|
|
1654
|
-
// />
|
|
1655
|
-
// {!isAiContentBotDisabled && (
|
|
1656
|
-
// <CapAskAira.ContentGenerationBot
|
|
1657
|
-
// text={fallbackMessage || ""}
|
|
1658
|
-
// setText={(text) => {
|
|
1659
|
-
// onFallbackMessageChange({ target: { value: text } });
|
|
1660
|
-
// }}
|
|
1661
|
-
// iconPlacement="float-br"
|
|
1662
|
-
// rootStyle={{
|
|
1663
|
-
// bottom: "0.5rem",
|
|
1664
|
-
// right: "0.5rem",
|
|
1665
|
-
// position: "absolute",
|
|
1666
|
-
// }}
|
|
1667
|
-
// />
|
|
1668
|
-
// )}
|
|
1669
|
-
// </div>
|
|
1670
|
-
// <CapRow>{fallbackSmsLength()}</CapRow>
|
|
1671
|
-
// </>
|
|
1672
|
-
// );
|
|
1673
|
-
|
|
1674
|
-
// return <>{contentArr}</>;
|
|
1675
|
-
};
|
|
1575
|
+
const renderFallBackSmsComponent = () => (
|
|
1576
|
+
<SmsFallback
|
|
1577
|
+
value={smsFallbackData}
|
|
1578
|
+
onChange={setSmsFallbackData}
|
|
1579
|
+
parentLocation={location}
|
|
1580
|
+
smsRegister={smsRegister}
|
|
1581
|
+
isFullMode={isFullMode}
|
|
1582
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
1583
|
+
channelsToHide={CHANNELS_TO_HIDE_FOR_SMS_ONLY}
|
|
1584
|
+
sectionTitle={
|
|
1585
|
+
smsFallbackData
|
|
1586
|
+
? formatMessage(messages.fallbackLabel)
|
|
1587
|
+
: formatMessage(messages.smsFallbackOptional)
|
|
1588
|
+
}
|
|
1589
|
+
templateListTitle={formatMessage(creativesMessages.creativeTemplates)}
|
|
1590
|
+
templateListDescription={formatMessage(creativesMessages.creativeTemplatesDesc)}
|
|
1591
|
+
/* Full-mode: card layout only while drafting a new template; after send for approval or when a template is loaded, use inline layout. */
|
|
1592
|
+
showAsCard={isFullMode && !isEditFlow && templateStatus === ''}
|
|
1593
|
+
disableSelectTemplate={isEditFlow}
|
|
1594
|
+
eventContextTags={eventContextTags}
|
|
1595
|
+
onRcsFallbackEditorStateChange={handleSmsFallbackEditorStateChange}
|
|
1596
|
+
isRcsEditFlow={isEditFlow}
|
|
1597
|
+
/>
|
|
1598
|
+
);
|
|
1676
1599
|
|
|
1677
1600
|
const uploadRcsImage = useCallback((file, type, fileParams, index) => {
|
|
1678
|
-
setImageError(null);
|
|
1679
1601
|
const isRcsThumbnail = index === 1;
|
|
1680
1602
|
actions.uploadRcsAsset(file, type, {
|
|
1681
1603
|
isRcsThumbnail,
|
|
@@ -1687,7 +1609,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1687
1609
|
|
|
1688
1610
|
const setUpdateRcsImageSrc = useCallback(
|
|
1689
1611
|
(val) => {
|
|
1690
|
-
setAssetListImage(val);
|
|
1691
1612
|
updateRcsImageSrc(val);
|
|
1692
1613
|
actions.clearRcsMediaAsset(0);
|
|
1693
1614
|
},
|
|
@@ -1717,7 +1638,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1717
1638
|
|
|
1718
1639
|
|
|
1719
1640
|
const uploadRcsVideo = (file, type, fileParams) => {
|
|
1720
|
-
setImageError(null);
|
|
1721
1641
|
actions.uploadRcsAsset(file, type, {
|
|
1722
1642
|
...fileParams,
|
|
1723
1643
|
type: 'video',
|
|
@@ -1726,9 +1646,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1726
1646
|
});
|
|
1727
1647
|
};
|
|
1728
1648
|
|
|
1729
|
-
const updateRcsVideoSrc = (val) => {
|
|
1730
|
-
setRcsVideoSrc(val);
|
|
1731
|
-
};
|
|
1732
1649
|
const setUpdateRcsVideoSrc = useCallback((index, val) => {
|
|
1733
1650
|
setRcsVideoSrc(val);
|
|
1734
1651
|
setAssetList(val);
|
|
@@ -1757,7 +1674,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1757
1674
|
<>
|
|
1758
1675
|
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
|
|
1759
1676
|
<CapImageUpload
|
|
1760
|
-
|
|
1677
|
+
className="cap-custom-image-upload rcs-image-upload--top-spacing"
|
|
1761
1678
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
1762
1679
|
imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
|
|
1763
1680
|
imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
|
|
@@ -1769,7 +1686,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1769
1686
|
updateOnReUpload={updateOnRcsThumbnailReUpload}
|
|
1770
1687
|
minImgSize={RCS_THUMBNAIL_MIN_SIZE}
|
|
1771
1688
|
index={1}
|
|
1772
|
-
className="cap-custom-image-upload"
|
|
1773
1689
|
key={`rcs-uploaded-image-${currentDimension}`}
|
|
1774
1690
|
imageData={thumbnailData}
|
|
1775
1691
|
channel={RCS}
|
|
@@ -1800,7 +1716,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1800
1716
|
value: dim.type,
|
|
1801
1717
|
label: `${dim.label}`
|
|
1802
1718
|
}))}
|
|
1803
|
-
|
|
1719
|
+
className="rcs-dimension-select--bottom-spacing"
|
|
1804
1720
|
/>
|
|
1805
1721
|
</>
|
|
1806
1722
|
)}
|
|
@@ -1814,7 +1730,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1814
1730
|
</div>
|
|
1815
1731
|
) : (
|
|
1816
1732
|
<CapImageUpload
|
|
1817
|
-
style={{ paddingTop: '20px' }}
|
|
1818
1733
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
1819
1734
|
imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
|
|
1820
1735
|
imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
|
|
@@ -1825,7 +1740,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1825
1740
|
updateImageSrc={setUpdateRcsImageSrc}
|
|
1826
1741
|
updateOnReUpload={updateOnRcsImageReUpload}
|
|
1827
1742
|
index={0}
|
|
1828
|
-
className="cap-custom-image-upload"
|
|
1743
|
+
className="cap-custom-image-upload rcs-image-upload--top-spacing"
|
|
1829
1744
|
key={`rcs-uploaded-image-${selectedDimension}`}
|
|
1830
1745
|
imageData={rcsData}
|
|
1831
1746
|
channel={RCS}
|
|
@@ -1855,7 +1770,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1855
1770
|
value: dim.type,
|
|
1856
1771
|
label: `${dim.label}`
|
|
1857
1772
|
}))}
|
|
1858
|
-
|
|
1773
|
+
className="rcs-dimension-select--bottom-spacing"
|
|
1859
1774
|
/>
|
|
1860
1775
|
)}
|
|
1861
1776
|
{(isEditFlow || !isFullMode) ? (
|
|
@@ -1915,8 +1830,16 @@ const splitTemplateVarString = (str) => {
|
|
|
1915
1830
|
const getRcsPreview = () => {
|
|
1916
1831
|
|
|
1917
1832
|
const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
|
|
1918
|
-
const
|
|
1919
|
-
const
|
|
1833
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
1834
|
+
const titleVarCountForResolve = isMediaTypeText
|
|
1835
|
+
? 0
|
|
1836
|
+
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
1837
|
+
const resolvedTitle = isMediaTypeText
|
|
1838
|
+
? ''
|
|
1839
|
+
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
1840
|
+
const resolvedDesc = isSlotMappingMode
|
|
1841
|
+
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
1842
|
+
: templateDesc;
|
|
1920
1843
|
return (
|
|
1921
1844
|
<UnifiedPreview
|
|
1922
1845
|
channel={RCS}
|
|
@@ -1939,51 +1862,65 @@ const splitTemplateVarString = (str) => {
|
|
|
1939
1862
|
);
|
|
1940
1863
|
};
|
|
1941
1864
|
|
|
1942
|
-
const getUnmappedDesc = (str, mapping) => {
|
|
1943
|
-
if (!str) return '';
|
|
1944
|
-
if (!mapping || Object.keys(mapping).length === 0) return str;
|
|
1945
|
-
let result = str;
|
|
1946
|
-
const replacements = [];
|
|
1947
|
-
Object.entries(mapping).forEach(([key, value]) => {
|
|
1948
|
-
const raw = (value ?? '').toString();
|
|
1949
|
-
if (!raw || raw?.trim?.() === '') return;
|
|
1950
|
-
const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
|
|
1951
|
-
replacements.push({ key, needle: raw });
|
|
1952
|
-
if (braced !== raw) replacements.push({ key, needle: braced });
|
|
1953
|
-
});
|
|
1954
|
-
const seen = new Set();
|
|
1955
|
-
const uniq = replacements
|
|
1956
|
-
.filter(({ key, needle }) => {
|
|
1957
|
-
const id = `${key}::${needle}`;
|
|
1958
|
-
if (seen.has(id)) return false;
|
|
1959
|
-
seen.add(id);
|
|
1960
|
-
return true;
|
|
1961
|
-
})
|
|
1962
|
-
.sort((a, b) => (b.needle.length - a.needle.length));
|
|
1963
|
-
|
|
1964
|
-
uniq.forEach(({ key, needle }) => {
|
|
1965
|
-
if (!needle) return;
|
|
1966
|
-
const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
1967
|
-
const regex = new RegExp(escaped, 'g');
|
|
1968
|
-
result = result.replace(regex, `{{${key}}}`);
|
|
1969
|
-
});
|
|
1970
|
-
return result;
|
|
1971
|
-
};
|
|
1972
|
-
|
|
1973
1865
|
const createPayload = () => {
|
|
1974
|
-
const
|
|
1975
|
-
const {
|
|
1976
|
-
template_id: templateId = '',
|
|
1977
|
-
template_name = '',
|
|
1978
|
-
'sms-editor': template = '',
|
|
1979
|
-
header: registeredSenderIds = [],
|
|
1980
|
-
} = base;
|
|
1981
|
-
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
|
|
1982
|
-
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
|
|
1866
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
1983
1867
|
const alignment = isMediaTypeImage
|
|
1984
1868
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
|
|
1985
1869
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
|
|
1986
1870
|
|
|
1871
|
+
const heightTypeForCardWidth = isMediaTypeText
|
|
1872
|
+
? undefined
|
|
1873
|
+
: isMediaTypeImage
|
|
1874
|
+
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType
|
|
1875
|
+
: isMediaTypeVideo
|
|
1876
|
+
? RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType
|
|
1877
|
+
: undefined;
|
|
1878
|
+
const cardWidthFromSelection =
|
|
1879
|
+
heightTypeForCardWidth === MEDIUM ? MEDIUM : SMALL;
|
|
1880
|
+
|
|
1881
|
+
/** Library: merge props + state so SMS fallback is not dropped when local state is empty but templateData has consumer data. */
|
|
1882
|
+
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
1883
|
+
const smsFallbackMerged = !isFullMode
|
|
1884
|
+
? (() => {
|
|
1885
|
+
const local =
|
|
1886
|
+
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
1887
|
+
return {
|
|
1888
|
+
...smsFromApiShape,
|
|
1889
|
+
...local,
|
|
1890
|
+
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
1891
|
+
};
|
|
1892
|
+
})()
|
|
1893
|
+
: (smsFallbackData || {});
|
|
1894
|
+
const smsFallbackForPayload = (() => {
|
|
1895
|
+
if (isFullMode) {
|
|
1896
|
+
return hasMeaningfulSmsFallbackShape(smsFallbackData) ? smsFallbackData : null;
|
|
1897
|
+
}
|
|
1898
|
+
const mapped = {
|
|
1899
|
+
templateName:
|
|
1900
|
+
smsFallbackMerged.templateName
|
|
1901
|
+
|| smsFallbackMerged.smsTemplateName
|
|
1902
|
+
|| '',
|
|
1903
|
+
// Use `||` so empty `content` does not block campaign/API `message` (common in embedded flows).
|
|
1904
|
+
content:
|
|
1905
|
+
smsFallbackMerged.content
|
|
1906
|
+
|| smsFallbackMerged.smsContent
|
|
1907
|
+
|| smsFallbackMerged.smsTemplateContent
|
|
1908
|
+
|| smsFallbackMerged.message
|
|
1909
|
+
|| '',
|
|
1910
|
+
templateContent:
|
|
1911
|
+
pickFirstSmsFallbackTemplateString(smsFallbackMerged)
|
|
1912
|
+
|| '',
|
|
1913
|
+
...(typeof smsFallbackMerged.unicodeValidity === 'boolean'
|
|
1914
|
+
&& { unicodeValidity: smsFallbackMerged.unicodeValidity }),
|
|
1915
|
+
...(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
|
|
1916
|
+
&& Object.keys(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
|
|
1917
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
|
|
1918
|
+
smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
|
|
1919
|
+
}),
|
|
1920
|
+
};
|
|
1921
|
+
return hasMeaningfulSmsFallbackShape(mapped) ? mapped : null;
|
|
1922
|
+
})();
|
|
1923
|
+
|
|
1987
1924
|
const payload = {
|
|
1988
1925
|
name: templateName,
|
|
1989
1926
|
versions: {
|
|
@@ -1995,12 +1932,14 @@ const splitTemplateVarString = (str) => {
|
|
|
1995
1932
|
cardSettings: {
|
|
1996
1933
|
cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
|
|
1997
1934
|
...(alignment && { mediaAlignment: alignment }),
|
|
1998
|
-
cardWidth:
|
|
1935
|
+
cardWidth: cardWidthFromSelection,
|
|
1999
1936
|
},
|
|
2000
1937
|
cardContent: [
|
|
2001
1938
|
{
|
|
2002
|
-
|
|
2003
|
-
|
|
1939
|
+
// Persist raw template copy + cardVarMapped — not resolveTemplateWithMap output — so library
|
|
1940
|
+
// / getFormData round-trip keeps {{…}} and slot values (resolved strings broke reopen hydration).
|
|
1941
|
+
title: templateTitle,
|
|
1942
|
+
description: templateDesc,
|
|
2004
1943
|
mediaType: templateMediaType,
|
|
2005
1944
|
...(!isMediaTypeText && {media: {
|
|
2006
1945
|
mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
|
|
@@ -2009,23 +1948,30 @@ const splitTemplateVarString = (str) => {
|
|
|
2009
1948
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
|
|
2010
1949
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
|
|
2011
1950
|
}}),
|
|
2012
|
-
...(
|
|
2013
|
-
const
|
|
2014
|
-
...(templateTitle
|
|
2015
|
-
...(templateDesc
|
|
1951
|
+
...(isSlotMappingMode && (() => {
|
|
1952
|
+
const templateVarTokens = [
|
|
1953
|
+
...(templateTitle?.match(rcsVarRegex) ?? []),
|
|
1954
|
+
...(templateDesc?.match(rcsVarRegex) ?? []),
|
|
2016
1955
|
];
|
|
2017
|
-
const
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
1956
|
+
const persistedSlotVarMap = {};
|
|
1957
|
+
const seenSemanticVarNames = new Set();
|
|
1958
|
+
templateVarTokens.forEach((token, slotIndexZeroBased) => {
|
|
1959
|
+
const varName = getVarNameFromToken(token);
|
|
1960
|
+
if (!varName) return;
|
|
1961
|
+
const resolvedRawValue = resolveCardVarMappedSlotValue(
|
|
1962
|
+
cardVarMapped,
|
|
1963
|
+
varName,
|
|
1964
|
+
slotIndexZeroBased,
|
|
1965
|
+
isSlotMappingMode,
|
|
1966
|
+
);
|
|
1967
|
+
const sanitizedSlotValue = sanitizeCardVarMappedValue(resolvedRawValue);
|
|
1968
|
+
persistedSlotVarMap[String(slotIndexZeroBased + 1)] = sanitizedSlotValue;
|
|
1969
|
+
if (!seenSemanticVarNames.has(varName)) {
|
|
1970
|
+
seenSemanticVarNames.add(varName);
|
|
1971
|
+
persistedSlotVarMap[varName] = sanitizedSlotValue;
|
|
2026
1972
|
}
|
|
2027
1973
|
});
|
|
2028
|
-
return { cardVarMapped:
|
|
1974
|
+
return { cardVarMapped: persistedSlotVarMap };
|
|
2029
1975
|
})()),
|
|
2030
1976
|
...(suggestions.length > 0 && { suggestions }),
|
|
2031
1977
|
}
|
|
@@ -2033,17 +1979,29 @@ const splitTemplateVarString = (str) => {
|
|
|
2033
1979
|
contentType: isFullMode ? templateType : RICHCARD,
|
|
2034
1980
|
...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
|
|
2035
1981
|
},
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
1982
|
+
...(smsFallbackForPayload && (() => {
|
|
1983
|
+
const smsBodyText =
|
|
1984
|
+
smsFallbackForPayload.content
|
|
1985
|
+
|| smsFallbackForPayload.templateContent
|
|
1986
|
+
|| smsFallbackForPayload.message
|
|
1987
|
+
|| smsFallbackForPayload.smsContent
|
|
1988
|
+
|| '';
|
|
1989
|
+
return {
|
|
1990
|
+
smsFallBackContent: {
|
|
1991
|
+
smsTemplateName: smsFallbackForPayload.templateName || '',
|
|
1992
|
+
smsContent: smsBodyText,
|
|
1993
|
+
// cap-campaigns-v2 `normalizeRcsMessageContentForApi` only serializes `message` (+ templateConfigs); without this key SMS fallback is dropped on send.
|
|
1994
|
+
message: smsBodyText,
|
|
1995
|
+
...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
|
|
1996
|
+
unicodeValidity: smsFallbackForPayload.unicodeValidity,
|
|
1997
|
+
}),
|
|
1998
|
+
...(smsFallbackForPayload.rcsSmsFallbackVarMapped
|
|
1999
|
+
&& Object.keys(smsFallbackForPayload.rcsSmsFallbackVarMapped).length > 0 && {
|
|
2000
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackForPayload.rcsSmsFallbackVarMapped,
|
|
2001
|
+
}),
|
|
2044
2002
|
},
|
|
2045
|
-
}
|
|
2046
|
-
},
|
|
2003
|
+
};
|
|
2004
|
+
})()),
|
|
2047
2005
|
},
|
|
2048
2006
|
},
|
|
2049
2007
|
},
|
|
@@ -2053,6 +2011,84 @@ const splitTemplateVarString = (str) => {
|
|
|
2053
2011
|
return payload;
|
|
2054
2012
|
};
|
|
2055
2013
|
|
|
2014
|
+
/** Shape expected by CommonTestAndPreview buildRcsTestMessagePayload (versions.base.content.RCS). */
|
|
2015
|
+
const testPreviewFormData = useMemo(() => {
|
|
2016
|
+
const payload = createPayload();
|
|
2017
|
+
const rcs = payload?.versions?.base?.content?.RCS;
|
|
2018
|
+
if (!rcs) return null;
|
|
2019
|
+
// createMessageMeta uses WeCRM `id` when present; else template API account id (sourceAccountIdentifier).
|
|
2020
|
+
const accountIdForCreateMessageMeta =
|
|
2021
|
+
(wecrmAccountId != null && String(wecrmAccountId).trim() !== '')
|
|
2022
|
+
? String(wecrmAccountId)
|
|
2023
|
+
: accountId;
|
|
2024
|
+
const rcsForTest = {
|
|
2025
|
+
...rcs,
|
|
2026
|
+
rcsContent: {
|
|
2027
|
+
...rcs.rcsContent,
|
|
2028
|
+
...(accountIdForCreateMessageMeta ? { accountId: accountIdForCreateMessageMeta } : {}),
|
|
2029
|
+
},
|
|
2030
|
+
};
|
|
2031
|
+
const out = {
|
|
2032
|
+
versions: {
|
|
2033
|
+
base: {
|
|
2034
|
+
content: {
|
|
2035
|
+
RCS: rcsForTest,
|
|
2036
|
+
},
|
|
2037
|
+
},
|
|
2038
|
+
},
|
|
2039
|
+
};
|
|
2040
|
+
const fb = smsFallbackData;
|
|
2041
|
+
if (fb && (fb.smsTemplateId || fb.templateContent || fb.content)) {
|
|
2042
|
+
out.templateConfigs = {
|
|
2043
|
+
templateId: fb.smsTemplateId || '',
|
|
2044
|
+
template: fb.templateContent || fb.content || '',
|
|
2045
|
+
traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister),
|
|
2046
|
+
registeredSenderIds: Array.isArray(fb.registeredSenderIds) ? fb.registeredSenderIds : [],
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
return out;
|
|
2050
|
+
}, [
|
|
2051
|
+
templateName,
|
|
2052
|
+
templateTitle,
|
|
2053
|
+
templateDesc,
|
|
2054
|
+
templateMediaType,
|
|
2055
|
+
cardVarMapped,
|
|
2056
|
+
suggestions,
|
|
2057
|
+
rcsImageSrc,
|
|
2058
|
+
rcsVideoSrc,
|
|
2059
|
+
rcsThumbnailSrc,
|
|
2060
|
+
selectedDimension,
|
|
2061
|
+
smsFallbackData,
|
|
2062
|
+
isFullMode,
|
|
2063
|
+
isEditFlow,
|
|
2064
|
+
templateType,
|
|
2065
|
+
accountId,
|
|
2066
|
+
wecrmAccountId,
|
|
2067
|
+
accessToken,
|
|
2068
|
+
accountName,
|
|
2069
|
+
hostName,
|
|
2070
|
+
smsRegister,
|
|
2071
|
+
]);
|
|
2072
|
+
|
|
2073
|
+
/**
|
|
2074
|
+
* Library/campaign: `createPayload` merges root + nested `smsFallBackContent` from `templateData`
|
|
2075
|
+
* with `smsFallbackData`. Done/slot validation must use the same merge — otherwise local state can
|
|
2076
|
+
* miss `templateContent` / var map while the parent payload still has them (DLT campaigns).
|
|
2077
|
+
*/
|
|
2078
|
+
const librarySmsFallbackMergedForValidation = useMemo(() => {
|
|
2079
|
+
if (isFullMode) {
|
|
2080
|
+
return smsFallbackData;
|
|
2081
|
+
}
|
|
2082
|
+
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
2083
|
+
const local =
|
|
2084
|
+
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
2085
|
+
return {
|
|
2086
|
+
...smsFromApiShape,
|
|
2087
|
+
...local,
|
|
2088
|
+
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
2089
|
+
};
|
|
2090
|
+
}, [isFullMode, templateData, smsFallbackData]);
|
|
2091
|
+
|
|
2056
2092
|
const actionCallback = ({ errorMessage, resp }, isEdit) => {
|
|
2057
2093
|
// eslint-disable-next-line no-undef
|
|
2058
2094
|
const error = errorMessage?.message || errorMessage;
|
|
@@ -2082,6 +2118,9 @@ const splitTemplateVarString = (str) => {
|
|
|
2082
2118
|
_id: params?.id,
|
|
2083
2119
|
validity: true,
|
|
2084
2120
|
type: RCS,
|
|
2121
|
+
// CreativesContainer closes the slide box *after* getCreativesData runs so the parent receives
|
|
2122
|
+
// the RCS payload first (closing immediately used to skip getCreativesData → empty "Add creative").
|
|
2123
|
+
closeSlideBoxAfterSubmit: !isFullMode,
|
|
2085
2124
|
};
|
|
2086
2125
|
getFormData(formDataParams);
|
|
2087
2126
|
};
|
|
@@ -2095,6 +2134,7 @@ const splitTemplateVarString = (str) => {
|
|
|
2095
2134
|
actionCallback({ resp, errorMessage });
|
|
2096
2135
|
setSpin(false); // Always turn off spinner
|
|
2097
2136
|
if (!errorMessage) {
|
|
2137
|
+
setTemplateStatus(RCS_STATUSES.pending);
|
|
2098
2138
|
onCreateComplete();
|
|
2099
2139
|
}
|
|
2100
2140
|
});
|
|
@@ -2106,6 +2146,64 @@ const splitTemplateVarString = (str) => {
|
|
|
2106
2146
|
}
|
|
2107
2147
|
};
|
|
2108
2148
|
|
|
2149
|
+
/** When a fallback SMS row exists, require non-empty body (trimmed) and filled var slots (DLT). */
|
|
2150
|
+
const smsFallbackBlocksDone = () => {
|
|
2151
|
+
// Non-DLT library: user removed SMS fallback (local null) but template still carries fallback — block Done.
|
|
2152
|
+
if (
|
|
2153
|
+
!isFullMode
|
|
2154
|
+
&& !isTraiDLTEnable(isFullMode, smsRegister)
|
|
2155
|
+
&& smsFallbackData == null
|
|
2156
|
+
&& hasMeaningfulSmsFallbackShape(
|
|
2157
|
+
getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
|
|
2158
|
+
)
|
|
2159
|
+
) {
|
|
2160
|
+
return true;
|
|
2161
|
+
}
|
|
2162
|
+
if (!smsFallbackData) return false;
|
|
2163
|
+
// Full-mode (Send for approval): SMS fallback is optional. Tag-slot mapping is a display/preview
|
|
2164
|
+
// concern, not a structural requirement for approval — the registered SMS template body stands on
|
|
2165
|
+
// its own. Never block the Send for approval button due to missing or unfilled fallback var slots.
|
|
2166
|
+
if (isFullMode) return false;
|
|
2167
|
+
const merged = librarySmsFallbackMergedForValidation;
|
|
2168
|
+
const templateText = pickFirstSmsFallbackTemplateString(merged);
|
|
2169
|
+
if (!templateText) {
|
|
2170
|
+
return true;
|
|
2171
|
+
}
|
|
2172
|
+
const rawVarMap =
|
|
2173
|
+
merged.rcsSmsFallbackVarMapped
|
|
2174
|
+
|| merged['rcs-sms-fallback-var-mapped'];
|
|
2175
|
+
const varMap =
|
|
2176
|
+
rawVarMap != null && typeof rawVarMap === 'object' ? rawVarMap : {};
|
|
2177
|
+
return !areAllRcsSmsFallbackVarSlotsFilled(templateText, varMap);
|
|
2178
|
+
};
|
|
2179
|
+
|
|
2180
|
+
/**
|
|
2181
|
+
* Library / campaigns (`!isFullMode`): card slots are often stored on numeric keys (`1`,`2`,…) while
|
|
2182
|
+
* semantic keys stay `""` from API round-trip. `resolveCardVarMappedSlotValue` matches createPayload
|
|
2183
|
+
* / preview — naive `cardVarMapped[name]` wrongly kept Done disabled for DLT.
|
|
2184
|
+
*/
|
|
2185
|
+
const isLibraryCampaignCardVarMappingIncomplete = () => {
|
|
2186
|
+
if (isFullMode) return false;
|
|
2187
|
+
const titleTokens = splitTemplateVarStringRcs(templateTitle).filter((elem) =>
|
|
2188
|
+
rcsVarTestRegex.test(elem),
|
|
2189
|
+
);
|
|
2190
|
+
const descTokens = splitTemplateVarStringRcs(templateDesc).filter((elem) =>
|
|
2191
|
+
rcsVarTestRegex.test(elem),
|
|
2192
|
+
);
|
|
2193
|
+
const orderedVarNames = [
|
|
2194
|
+
...titleTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
|
|
2195
|
+
...descTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
|
|
2196
|
+
];
|
|
2197
|
+
if (orderedVarNames.length > 0 && isEmpty(cardVarMapped)) {
|
|
2198
|
+
return true;
|
|
2199
|
+
}
|
|
2200
|
+
return orderedVarNames.some((name, globalIdx) => {
|
|
2201
|
+
const v = resolveCardVarMappedSlotValue(cardVarMapped, name, globalIdx, true);
|
|
2202
|
+
const s = v == null ? '' : String(v);
|
|
2203
|
+
return s.trim() === '';
|
|
2204
|
+
});
|
|
2205
|
+
};
|
|
2206
|
+
|
|
2109
2207
|
const isDisableDone = () => {
|
|
2110
2208
|
if(isEditFlow){
|
|
2111
2209
|
return false;
|
|
@@ -2116,40 +2214,16 @@ const splitTemplateVarString = (str) => {
|
|
|
2116
2214
|
}
|
|
2117
2215
|
}
|
|
2118
2216
|
|
|
2119
|
-
if(
|
|
2120
|
-
|
|
2121
|
-
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2122
|
-
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
2123
|
-
|
|
2124
|
-
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2125
|
-
return true;
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
const hasEmptyMapping =
|
|
2129
|
-
cardVarMapped &&
|
|
2130
|
-
Object.keys(cardVarMapped).length > 0 &&
|
|
2131
|
-
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2132
|
-
if (typeof v !== 'string') return !v; // null/undefined
|
|
2133
|
-
return v.trim() === ''; // empty string
|
|
2134
|
-
});
|
|
2135
|
-
|
|
2136
|
-
if (hasEmptyMapping) {
|
|
2137
|
-
return true;
|
|
2138
|
-
}
|
|
2139
|
-
|
|
2140
|
-
const anyMissing = allVars.some(name => {
|
|
2141
|
-
const v = cardVarMapped?.[name];
|
|
2142
|
-
if (typeof v !== 'string') return !v;
|
|
2143
|
-
return v.trim() === '';
|
|
2144
|
-
});
|
|
2145
|
-
if (anyMissing) {
|
|
2146
|
-
return true;
|
|
2147
|
-
}
|
|
2217
|
+
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
2218
|
+
return true;
|
|
2148
2219
|
}
|
|
2149
2220
|
|
|
2150
|
-
|
|
2221
|
+
if (smsFallbackBlocksDone()) {
|
|
2151
2222
|
return true;
|
|
2223
|
+
}
|
|
2152
2224
|
|
|
2225
|
+
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
2226
|
+
return true;
|
|
2153
2227
|
}
|
|
2154
2228
|
if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
2155
2229
|
return true;
|
|
@@ -2167,53 +2241,36 @@ const splitTemplateVarString = (str) => {
|
|
|
2167
2241
|
return true;
|
|
2168
2242
|
}
|
|
2169
2243
|
}
|
|
2170
|
-
if (templateDescError || templateTitleError
|
|
2244
|
+
if (templateDescError || templateTitleError) {
|
|
2245
|
+
return true;
|
|
2246
|
+
}
|
|
2247
|
+
if (
|
|
2248
|
+
smsFallbackData?.content
|
|
2249
|
+
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
2250
|
+
) {
|
|
2171
2251
|
return true;
|
|
2172
2252
|
}
|
|
2173
2253
|
return false;
|
|
2174
2254
|
};
|
|
2175
2255
|
|
|
2176
2256
|
const isEditDisableDone = () => {
|
|
2177
|
-
|
|
2178
2257
|
if (templateStatus !== RCS_STATUSES.approved) {
|
|
2179
2258
|
return true;
|
|
2180
2259
|
}
|
|
2181
2260
|
|
|
2182
|
-
if (!isFullMode) {
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2261
|
+
// if (!isFullMode) {
|
|
2262
|
+
// if (templateName.trim() === '' || templateNameError) {
|
|
2263
|
+
// return true;
|
|
2264
|
+
// }
|
|
2265
|
+
// }
|
|
2266
|
+
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
2267
|
+
return true;
|
|
2186
2268
|
}
|
|
2187
|
-
if(!isFullMode){
|
|
2188
|
-
const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2189
|
-
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2190
|
-
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
2191
|
-
|
|
2192
|
-
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2193
|
-
return true;
|
|
2194
|
-
}
|
|
2195
|
-
|
|
2196
|
-
const hasEmptyMapping =
|
|
2197
|
-
cardVarMapped &&
|
|
2198
|
-
Object.keys(cardVarMapped).length > 0 &&
|
|
2199
|
-
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2200
|
-
if (typeof v !== 'string') return !v; // null/undefined
|
|
2201
|
-
return v.trim() === ''; // empty string
|
|
2202
|
-
});
|
|
2203
2269
|
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
}
|
|
2207
|
-
|
|
2208
|
-
const anyMissing = allVars.some(name => {
|
|
2209
|
-
const v = cardVarMapped?.[name];
|
|
2210
|
-
if (typeof v !== 'string') return !v;
|
|
2211
|
-
return v.trim() === '';
|
|
2212
|
-
});
|
|
2213
|
-
if (anyMissing) {
|
|
2214
|
-
return true;
|
|
2215
|
-
}
|
|
2270
|
+
if (smsFallbackBlocksDone()) {
|
|
2271
|
+
return true;
|
|
2216
2272
|
}
|
|
2273
|
+
|
|
2217
2274
|
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
2218
2275
|
return true;
|
|
2219
2276
|
}
|
|
@@ -2232,7 +2289,13 @@ const splitTemplateVarString = (str) => {
|
|
|
2232
2289
|
return true;
|
|
2233
2290
|
}
|
|
2234
2291
|
}
|
|
2235
|
-
if (templateTitleError || templateDescError
|
|
2292
|
+
if (templateTitleError || templateDescError) {
|
|
2293
|
+
return true;
|
|
2294
|
+
}
|
|
2295
|
+
if (
|
|
2296
|
+
smsFallbackData?.content
|
|
2297
|
+
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
2298
|
+
) {
|
|
2236
2299
|
return true;
|
|
2237
2300
|
}
|
|
2238
2301
|
return false;
|
|
@@ -2282,52 +2345,56 @@ const splitTemplateVarString = (str) => {
|
|
|
2282
2345
|
};
|
|
2283
2346
|
|
|
2284
2347
|
const getMainContent = () => {
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
return (
|
|
2289
|
-
<CapSlideBox
|
|
2290
|
-
show={showDltContainer}
|
|
2291
|
-
header={dltHeader}
|
|
2292
|
-
content={dltContent}
|
|
2293
|
-
handleClose={closeDltContainerHandler}
|
|
2294
|
-
size="size-xl"
|
|
2295
|
-
/>
|
|
2296
|
-
);
|
|
2297
|
-
}
|
|
2348
|
+
// Slideboxes are rendered outside the page-level spinner to avoid
|
|
2349
|
+
// stacking/blur issues during initial loads.
|
|
2350
|
+
if (showDltContainer) return null;
|
|
2298
2351
|
|
|
2299
2352
|
return (
|
|
2300
2353
|
<>
|
|
2301
|
-
{templateStatus !== '' && (
|
|
2302
|
-
<
|
|
2303
|
-
{
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2354
|
+
{templateStatus !== '' && (
|
|
2355
|
+
<CapRow className="template-status-container">
|
|
2356
|
+
<CapColumn span={14}>
|
|
2357
|
+
<CapLabel type="label2">
|
|
2358
|
+
{formatMessage(messages.templateStatusLabel)}
|
|
2359
|
+
</CapLabel>
|
|
2360
|
+
|
|
2361
|
+
{templateStatus && (
|
|
2362
|
+
<CapAlert
|
|
2363
|
+
message={getTemplateStatusMessage()}
|
|
2364
|
+
type={getTemplateStatusType(templateStatus)}
|
|
2365
|
+
/>
|
|
2366
|
+
)}
|
|
2367
|
+
</CapColumn>
|
|
2368
|
+
</CapRow>
|
|
2313
2369
|
)}
|
|
2314
|
-
<CapRow className=
|
|
2370
|
+
<CapRow className={`cap-rcs-creatives ${isEditLike ? 'rcs-edit-mode' : ''}`}>
|
|
2315
2371
|
<CapColumn span={14}>
|
|
2316
2372
|
{/* template name */}
|
|
2317
2373
|
{isFullMode && (
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2374
|
+
isEditFlow ? (
|
|
2375
|
+
<div className="rcs-creative-name-readonly">
|
|
2376
|
+
<CapHeading type="h4">
|
|
2377
|
+
{formatMessage(globalMessages.creativeNameLabel)}
|
|
2378
|
+
</CapHeading>
|
|
2379
|
+
<CapHeading type="h5" className="rcs-creative-name-value">
|
|
2380
|
+
{templateName || '-'}
|
|
2381
|
+
</CapHeading>
|
|
2382
|
+
</div>
|
|
2383
|
+
) : (
|
|
2384
|
+
<CapInput
|
|
2385
|
+
id="rcs_template_name_input"
|
|
2386
|
+
data-testid="template_name"
|
|
2387
|
+
onChange={onTemplateNameChange}
|
|
2388
|
+
errorMessage={templateNameError}
|
|
2389
|
+
placeholder={formatMessage(
|
|
2390
|
+
globalMessages.templateNamePlaceholder,
|
|
2391
|
+
)}
|
|
2392
|
+
value={templateName || ''}
|
|
2393
|
+
size="default"
|
|
2394
|
+
label={formatMessage(globalMessages.creativeNameLabel)}
|
|
2395
|
+
disabled={(isEditFlow || !isFullMode)}
|
|
2396
|
+
/>
|
|
2397
|
+
)
|
|
2331
2398
|
)}
|
|
2332
2399
|
{renderLabel('templateTypeLabel')}
|
|
2333
2400
|
<CapRadioGroup
|
|
@@ -2355,7 +2422,7 @@ const splitTemplateVarString = (str) => {
|
|
|
2355
2422
|
</>
|
|
2356
2423
|
)}
|
|
2357
2424
|
{renderTextComponent()}
|
|
2358
|
-
<CapDivider
|
|
2425
|
+
<CapDivider className="rcs-fallback-section-divider" />
|
|
2359
2426
|
{renderFallBackSmsComponent()}
|
|
2360
2427
|
<div className="rcs-scroll-div" />
|
|
2361
2428
|
</CapColumn>
|
|
@@ -2367,7 +2434,8 @@ const splitTemplateVarString = (str) => {
|
|
|
2367
2434
|
|
|
2368
2435
|
|
|
2369
2436
|
<div className="rcs-footer">
|
|
2370
|
-
{!
|
|
2437
|
+
{/* Full-mode create only: send-for-approval + disabled test/preview. Library mode uses Done below — onDoneCallback() is undefined when !isFullMode, so do not render this row (avoids a no-op primary button). */}
|
|
2438
|
+
{!isEditFlow && isFullMode && (
|
|
2371
2439
|
<>
|
|
2372
2440
|
<div className="button-disabled-tooltip-wrapper">
|
|
2373
2441
|
<CapButton
|
|
@@ -2388,7 +2456,6 @@ const splitTemplateVarString = (str) => {
|
|
|
2388
2456
|
className="rcs-test-preview-btn"
|
|
2389
2457
|
type="secondary"
|
|
2390
2458
|
disabled={true}
|
|
2391
|
-
style={{ marginLeft: "8px" }}
|
|
2392
2459
|
>
|
|
2393
2460
|
<FormattedMessage {...creativesMessages.testAndPreview} />
|
|
2394
2461
|
</CapButton>
|
|
@@ -2421,51 +2488,6 @@ const splitTemplateVarString = (str) => {
|
|
|
2421
2488
|
</>
|
|
2422
2489
|
)}
|
|
2423
2490
|
</div>
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
{fallbackPreviewmode && (
|
|
2427
|
-
<CapSlideBox
|
|
2428
|
-
className="rcs-fallback-preview"
|
|
2429
|
-
show={fallbackPreviewmode}
|
|
2430
|
-
header={(
|
|
2431
|
-
<CapHeading type="h7" style={{ color: CAP_G01 }}>
|
|
2432
|
-
{formatMessage(messages.fallbackPreviewtitle)}
|
|
2433
|
-
</CapHeading>
|
|
2434
|
-
)}
|
|
2435
|
-
content={(
|
|
2436
|
-
<>
|
|
2437
|
-
<UnifiedPreview
|
|
2438
|
-
channel={RCS}
|
|
2439
|
-
content={{
|
|
2440
|
-
rcsPreviewContent: {
|
|
2441
|
-
rcsDesc: tempMsg,
|
|
2442
|
-
},
|
|
2443
|
-
}}
|
|
2444
|
-
device={ANDROID}
|
|
2445
|
-
showDeviceToggle={false}
|
|
2446
|
-
showHeader={false}
|
|
2447
|
-
formatMessage={formatMessage}
|
|
2448
|
-
/>
|
|
2449
|
-
<CapHeading
|
|
2450
|
-
type="h3"
|
|
2451
|
-
style={{ textAlign: 'center' }}
|
|
2452
|
-
className="margin-t-16"
|
|
2453
|
-
>
|
|
2454
|
-
{formatMessage(messages.totalCharacters, {
|
|
2455
|
-
smsCount: Math.ceil(
|
|
2456
|
-
tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
2457
|
-
),
|
|
2458
|
-
number: tempMsg.length,
|
|
2459
|
-
})}
|
|
2460
|
-
</CapHeading>
|
|
2461
|
-
</>
|
|
2462
|
-
)}
|
|
2463
|
-
handleClose={() => {
|
|
2464
|
-
setFallbackPreviewmode(false);
|
|
2465
|
-
setDltPreviewData('');
|
|
2466
|
-
}}
|
|
2467
|
-
/>
|
|
2468
|
-
)}
|
|
2469
2491
|
</>
|
|
2470
2492
|
);
|
|
2471
2493
|
};
|
|
@@ -2474,12 +2496,38 @@ const splitTemplateVarString = (str) => {
|
|
|
2474
2496
|
<CapSpin spinning={loadingTags || spin}>
|
|
2475
2497
|
{getMainContent()}
|
|
2476
2498
|
</CapSpin>
|
|
2499
|
+
|
|
2500
|
+
{showDltContainer && (() => {
|
|
2501
|
+
const { dltHeader = '', dltContent = '' } = getDltSlideBoxContent() || {};
|
|
2502
|
+
return (
|
|
2503
|
+
<CapSlideBox
|
|
2504
|
+
show={showDltContainer}
|
|
2505
|
+
header={dltHeader}
|
|
2506
|
+
content={dltContent}
|
|
2507
|
+
handleClose={closeDltContainerHandler}
|
|
2508
|
+
size="size-xl"
|
|
2509
|
+
/>
|
|
2510
|
+
);
|
|
2511
|
+
})()}
|
|
2512
|
+
|
|
2477
2513
|
<TestAndPreviewSlidebox
|
|
2478
2514
|
show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
|
|
2479
2515
|
onClose={handleCloseTestAndPreview}
|
|
2480
|
-
formData={
|
|
2481
|
-
content={
|
|
2516
|
+
formData={testPreviewFormData}
|
|
2517
|
+
content={testAndPreviewContent}
|
|
2482
2518
|
currentChannel={RCS}
|
|
2519
|
+
orgUnitId={orgUnitId}
|
|
2520
|
+
smsFallbackContent={
|
|
2521
|
+
smsFallbackData && (smsFallbackData.templateContent || smsFallbackData.content)
|
|
2522
|
+
? {
|
|
2523
|
+
templateContent:
|
|
2524
|
+
smsFallbackData.templateContent || smsFallbackData.content || '',
|
|
2525
|
+
templateName: smsFallbackData.templateName || '',
|
|
2526
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackData?.rcsSmsFallbackVarMapped ?? {},
|
|
2527
|
+
}
|
|
2528
|
+
: null
|
|
2529
|
+
}
|
|
2530
|
+
smsRegister={smsRegister}
|
|
2483
2531
|
/>
|
|
2484
2532
|
</>
|
|
2485
2533
|
);
|
|
@@ -2491,7 +2539,6 @@ const mapStateToProps = createStructuredSelector({
|
|
|
2491
2539
|
metaEntities: makeSelectMetaEntities(),
|
|
2492
2540
|
loadingTags: isLoadingMetaEntities(),
|
|
2493
2541
|
injectedTags: setInjectedTags(),
|
|
2494
|
-
currentOrgDetails: selectCurrentOrgDetails(),
|
|
2495
2542
|
});
|
|
2496
2543
|
|
|
2497
2544
|
const mapDispatchToProps = (dispatch) => ({
|