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,11 +1,27 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import {CapIcon, CapImage, CapLabel, CapDivider } from '@capillarytech/cap-ui-library';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
RCS,
|
|
5
|
+
RCS_MEDIA_TYPES,
|
|
6
|
+
RCS_NUMERIC_VAR_NAME_REGEX,
|
|
7
|
+
RCS_REGEX_META_CHARS_PATTERN,
|
|
8
|
+
RCS_STRIP_MUSTACHE_DELIMITERS_REGEX,
|
|
9
|
+
} from './constants';
|
|
4
10
|
import './index.scss';
|
|
5
11
|
// import { formatMessage } from '../../../utils/intl';
|
|
6
12
|
import messages from './messages';
|
|
7
|
-
import {
|
|
8
|
-
|
|
13
|
+
import {
|
|
14
|
+
STATUS_OPTIONS,
|
|
15
|
+
RCS_BUTTON_TYPES,
|
|
16
|
+
RCS_STATUSES,
|
|
17
|
+
rcsVarRegex,
|
|
18
|
+
rcsVarTestRegex,
|
|
19
|
+
} from './constants';
|
|
20
|
+
import {
|
|
21
|
+
splitTemplateVarString,
|
|
22
|
+
COMBINED_SMS_TEMPLATE_VAR_REGEX,
|
|
23
|
+
isAnyTemplateVarToken,
|
|
24
|
+
} from '../../utils/templateVarUtils';
|
|
9
25
|
|
|
10
26
|
export const getRcsStatusType = (status) => {
|
|
11
27
|
switch (status) {
|
|
@@ -33,6 +49,334 @@ export const getTemplateStatusType = (templateStatus) => {
|
|
|
33
49
|
}
|
|
34
50
|
};
|
|
35
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Global RegExp matching `{{numericVarName}}` in RCS template strings.
|
|
54
|
+
* `numericVarName` is escaped for regex metacharacters.
|
|
55
|
+
*/
|
|
56
|
+
export function buildRcsNumericMustachePlaceholderRegex(numericVarName) {
|
|
57
|
+
const escaped = String(numericVarName).replace(RCS_REGEX_META_CHARS_PATTERN, '\\$&');
|
|
58
|
+
return new RegExp(`\\{\\{${escaped}\\}\\}`, 'g');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function normalizeCardVarMapped(rawCardVarMapped, orderedTagNames) {
|
|
62
|
+
if (!rawCardVarMapped || typeof rawCardVarMapped !== 'object') return {};
|
|
63
|
+
const normalizedMap = {};
|
|
64
|
+
const templateVarNamesInOrder = Array.isArray(orderedTagNames) ? orderedTagNames : null;
|
|
65
|
+
const hasOrderedSlots =
|
|
66
|
+
Boolean(templateVarNamesInOrder?.length);
|
|
67
|
+
Object.entries(rawCardVarMapped).forEach(([entryKey, entryValue]) => {
|
|
68
|
+
const trimmedValue = entryValue == null ? '' : String(entryValue).trim();
|
|
69
|
+
const entryKeyIsNumericSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(entryKey));
|
|
70
|
+
const mustacheInnerMatch = trimmedValue.match(/^\{\{([^}]+)\}\}$/);
|
|
71
|
+
const innerFromMustache =
|
|
72
|
+
mustacheInnerMatch?.[1] != null ? String(mustacheInnerMatch[1]).trim() : null;
|
|
73
|
+
|
|
74
|
+
if (innerFromMustache !== null && entryKeyIsNumericSlot) {
|
|
75
|
+
const slotIndexZeroBased = parseInt(String(entryKey), 10) - 1;
|
|
76
|
+
const expectedVarForSlot =
|
|
77
|
+
hasOrderedSlots
|
|
78
|
+
&& slotIndexZeroBased >= 0
|
|
79
|
+
&& slotIndexZeroBased < templateVarNamesInOrder.length
|
|
80
|
+
? templateVarNamesInOrder[slotIndexZeroBased]
|
|
81
|
+
: null;
|
|
82
|
+
const innerMatchesSlotToken =
|
|
83
|
+
expectedVarForSlot != null && innerFromMustache === expectedVarForSlot;
|
|
84
|
+
const legacyUnorderedPlaceholder = !hasOrderedSlots;
|
|
85
|
+
/* Library: slot "1" + {{user_id_b64}} when token is user_id_b64 → empty semantic. With ordered
|
|
86
|
+
* slots, only clear when inner matches that slot's template token; else keep (e.g. {{1}}+{{FirstName}}). */
|
|
87
|
+
const clearNumericSlotMustacheAsUnfilled =
|
|
88
|
+
!RCS_NUMERIC_VAR_NAME_REGEX.test(innerFromMustache)
|
|
89
|
+
&& (legacyUnorderedPlaceholder || innerMatchesSlotToken);
|
|
90
|
+
if (clearNumericSlotMustacheAsUnfilled) {
|
|
91
|
+
const outputKey = innerFromMustache;
|
|
92
|
+
const existingValue = normalizedMap[outputKey];
|
|
93
|
+
if (existingValue != null && String(existingValue).trim() !== '') {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
normalizedMap[outputKey] = '';
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (RCS_NUMERIC_VAR_NAME_REGEX.test(innerFromMustache)) {
|
|
100
|
+
const outputKey = innerFromMustache;
|
|
101
|
+
const existingValue = normalizedMap[outputKey];
|
|
102
|
+
if (existingValue != null && String(existingValue).trim() !== '') {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
normalizedMap[outputKey] = '';
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (innerFromMustache !== null && !entryKeyIsNumericSlot) {
|
|
111
|
+
if (innerFromMustache === String(entryKey)) {
|
|
112
|
+
const existingValue = normalizedMap[entryKey];
|
|
113
|
+
if (existingValue != null && String(existingValue).trim() !== '') {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
normalizedMap[entryKey] = '';
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (entryKeyIsNumericSlot && templateVarNamesInOrder?.length) {
|
|
122
|
+
const slotIndexZeroBased = parseInt(String(entryKey), 10) - 1;
|
|
123
|
+
if (slotIndexZeroBased >= 0 && slotIndexZeroBased < templateVarNamesInOrder.length) {
|
|
124
|
+
normalizedMap[templateVarNamesInOrder[slotIndexZeroBased]] = trimmedValue;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
normalizedMap[entryKey] = trimmedValue;
|
|
129
|
+
});
|
|
130
|
+
return normalizedMap;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Rebuild `cardVarMapped` so keys match the current title/description tokens (title tokens first,
|
|
135
|
+
* then description), in order. Values are taken from the matching key, else from legacy slot
|
|
136
|
+
* `1`, `2`, … by index. If there are no `{{...}}` tokens, returns a shallow clone of `raw`.
|
|
137
|
+
*/
|
|
138
|
+
export function coalesceCardVarMappedToTemplate(
|
|
139
|
+
sourceCardVarMap,
|
|
140
|
+
templateTitle,
|
|
141
|
+
templateDesc,
|
|
142
|
+
rcsVarRegex,
|
|
143
|
+
) {
|
|
144
|
+
const getVarNameFromToken = (token = '') => token.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '');
|
|
145
|
+
const templateVarTokens = [
|
|
146
|
+
...(templateTitle?.match(rcsVarRegex) ?? []),
|
|
147
|
+
...(templateDesc?.match(rcsVarRegex) ?? []),
|
|
148
|
+
];
|
|
149
|
+
const lookupSourceMap =
|
|
150
|
+
sourceCardVarMap != null && typeof sourceCardVarMap === 'object' ? sourceCardVarMap : {};
|
|
151
|
+
if (!templateVarTokens.length) {
|
|
152
|
+
return { ...lookupSourceMap };
|
|
153
|
+
}
|
|
154
|
+
const coalescedMap = { ...lookupSourceMap };
|
|
155
|
+
const seenSemanticVarNames = new Set();
|
|
156
|
+
templateVarTokens.forEach((token, slotIndexZeroBased) => {
|
|
157
|
+
const semanticVarName = getVarNameFromToken(token);
|
|
158
|
+
if (!semanticVarName) return;
|
|
159
|
+
const numericSlotKey = String(slotIndexZeroBased + 1);
|
|
160
|
+
let valueFromSource = lookupSourceMap[numericSlotKey];
|
|
161
|
+
if (valueFromSource === undefined || valueFromSource === null) {
|
|
162
|
+
valueFromSource = lookupSourceMap[semanticVarName];
|
|
163
|
+
}
|
|
164
|
+
if (valueFromSource === undefined || valueFromSource === null) {
|
|
165
|
+
valueFromSource = lookupSourceMap[String(slotIndexZeroBased + 1)];
|
|
166
|
+
}
|
|
167
|
+
if (valueFromSource === undefined || valueFromSource === null) {
|
|
168
|
+
valueFromSource = lookupSourceMap[slotIndexZeroBased + 1];
|
|
169
|
+
}
|
|
170
|
+
const trimmedSlotValue = valueFromSource == null ? '' : String(valueFromSource).trim();
|
|
171
|
+
coalescedMap[numericSlotKey] = trimmedSlotValue;
|
|
172
|
+
if (!seenSemanticVarNames.has(semanticVarName)) {
|
|
173
|
+
seenSemanticVarNames.add(semanticVarName);
|
|
174
|
+
coalescedMap[semanticVarName] = trimmedSlotValue;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
return coalescedMap;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Resolve the personalization value for a variable slot — aligned with createPayload:
|
|
182
|
+
* per-slot numeric keys `1`, `2`, … win over legacy semantic keys when both exist (duplicate
|
|
183
|
+
* `{{name}}` in title+desc). If semantic is explicitly cleared to '', that still wins over a
|
|
184
|
+
* stale numeric value (see tests) — except in embedded library / journey mode (`isLibraryMode`).
|
|
185
|
+
*
|
|
186
|
+
* In library mode, campaign payloads often set semantic keys to '' while numeric slot `1`,`2`,…
|
|
187
|
+
* still holds the value selected in the library; prefer that so VarSegment prepopulates.
|
|
188
|
+
*
|
|
189
|
+
* When a numeric slot is present but only whitespace / empty (common after hydration), do not
|
|
190
|
+
* treat it as authoritative — fall through to the semantic key so preview and payload match the
|
|
191
|
+
* tag the user selected (e.g. `1: ''` but `promotion_points: '{{newTag}}'`).
|
|
192
|
+
*/
|
|
193
|
+
export function resolveCardVarMappedSlotValue(
|
|
194
|
+
cardVarMapped,
|
|
195
|
+
varName,
|
|
196
|
+
globalSlotIndexZeroBased,
|
|
197
|
+
isLibraryMode = false,
|
|
198
|
+
) {
|
|
199
|
+
const varMap = cardVarMapped ?? {};
|
|
200
|
+
const slotKey = String(globalSlotIndexZeroBased + 1);
|
|
201
|
+
const semanticEmpty =
|
|
202
|
+
Object.prototype.hasOwnProperty.call(varMap, varName)
|
|
203
|
+
&& String(varMap[varName] ?? '') === '';
|
|
204
|
+
const slotNonEmpty =
|
|
205
|
+
Object.prototype.hasOwnProperty.call(varMap, slotKey)
|
|
206
|
+
&& String(varMap[slotKey] ?? '').trim() !== '';
|
|
207
|
+
|
|
208
|
+
if (semanticEmpty && !(isLibraryMode && slotNonEmpty)) {
|
|
209
|
+
return '';
|
|
210
|
+
}
|
|
211
|
+
let numericSlotValue = '';
|
|
212
|
+
if (Object.prototype.hasOwnProperty.call(varMap, slotKey)) {
|
|
213
|
+
numericSlotValue = String(varMap[slotKey] ?? '');
|
|
214
|
+
} else if (Object.prototype.hasOwnProperty.call(varMap, globalSlotIndexZeroBased + 1)) {
|
|
215
|
+
numericSlotValue = String(varMap[globalSlotIndexZeroBased + 1] ?? '');
|
|
216
|
+
}
|
|
217
|
+
if (numericSlotValue.trim() !== '') {
|
|
218
|
+
return numericSlotValue;
|
|
219
|
+
}
|
|
220
|
+
if (Object.prototype.hasOwnProperty.call(varMap, varName)) {
|
|
221
|
+
return String(varMap[varName] ?? '');
|
|
222
|
+
}
|
|
223
|
+
return '';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Text-only RCS card: editor shows a single “Text message” field (description); title row is hidden. */
|
|
227
|
+
export function isRcsTextOnlyCardMediaType(mediaType) {
|
|
228
|
+
return (
|
|
229
|
+
mediaType === RCS_MEDIA_TYPES.NONE
|
|
230
|
+
|| String(mediaType || '').toUpperCase() === 'TEXT'
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Resolve RCS card title/description for TemplatePreview (e.g. campaign slidebox preview).
|
|
236
|
+
* Mirrors `resolveTemplateWithMap` in the Rcs editor: title vars use global slots 0..n-1, then description.
|
|
237
|
+
* For text-only cards (`textOnlyCard`), ignore persisted `title` and resolve description from slot 0 — matches
|
|
238
|
+
* the editor where only the message body is shown.
|
|
239
|
+
*/
|
|
240
|
+
export function resolveRcsCardPreviewStrings(
|
|
241
|
+
title,
|
|
242
|
+
description,
|
|
243
|
+
cardVarMapped,
|
|
244
|
+
isLibraryMode = false,
|
|
245
|
+
textOnlyCard = false,
|
|
246
|
+
) {
|
|
247
|
+
const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
|
|
248
|
+
const getVarNameFromToken = (token = '') =>
|
|
249
|
+
token.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '');
|
|
250
|
+
|
|
251
|
+
const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
|
|
252
|
+
if (!str) return '';
|
|
253
|
+
const arr = splitTemplateVarStringRcs(str);
|
|
254
|
+
let varOrdinal = 0;
|
|
255
|
+
return arr
|
|
256
|
+
.map((elem) => {
|
|
257
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
258
|
+
const key = getVarNameFromToken(elem);
|
|
259
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
260
|
+
varOrdinal += 1;
|
|
261
|
+
const v = resolveCardVarMappedSlotValue(
|
|
262
|
+
cardVarMapped,
|
|
263
|
+
key,
|
|
264
|
+
globalSlot,
|
|
265
|
+
isLibraryMode,
|
|
266
|
+
);
|
|
267
|
+
if (v == null || String(v).trim() === '') return elem;
|
|
268
|
+
return String(v);
|
|
269
|
+
}
|
|
270
|
+
return elem;
|
|
271
|
+
})
|
|
272
|
+
.join('');
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const effectiveTitle = textOnlyCard ? '' : String(title || '');
|
|
276
|
+
const titleVarCount = textOnlyCard
|
|
277
|
+
? 0
|
|
278
|
+
: (effectiveTitle.match(rcsVarRegex) || []).length;
|
|
279
|
+
return {
|
|
280
|
+
rcsTitle: textOnlyCard ? '' : resolveTemplateWithMap(effectiveTitle, 0),
|
|
281
|
+
rcsDesc: resolveTemplateWithMap(String(description || ''), titleVarCount),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Campaign consumer payload: replace each card's `title` / `description` with VarSegment-resolved
|
|
287
|
+
* tag strings (same rules as {@link resolveRcsCardPreviewStrings}). Root `rcsCardVarMapped` merges
|
|
288
|
+
* with per-card `cardVarMapped`; `cardVarMapped` on each card is left unchanged for round-trip.
|
|
289
|
+
*/
|
|
290
|
+
export function mapRcsCardContentForConsumerWithResolvedTags(
|
|
291
|
+
cardContentArray,
|
|
292
|
+
rootRcsCardVarMapped,
|
|
293
|
+
isFullMode,
|
|
294
|
+
) {
|
|
295
|
+
const rootRecord =
|
|
296
|
+
rootRcsCardVarMapped != null && typeof rootRcsCardVarMapped === 'object'
|
|
297
|
+
? rootRcsCardVarMapped
|
|
298
|
+
: {};
|
|
299
|
+
const list = Array.isArray(cardContentArray) ? cardContentArray : [];
|
|
300
|
+
const isLibraryMode = isFullMode !== true;
|
|
301
|
+
return list.map((card) => {
|
|
302
|
+
if (!card || typeof card !== 'object') return card;
|
|
303
|
+
const nested =
|
|
304
|
+
card.cardVarMapped != null && typeof card.cardVarMapped === 'object'
|
|
305
|
+
? card.cardVarMapped
|
|
306
|
+
: {};
|
|
307
|
+
const merged = { ...rootRecord, ...nested };
|
|
308
|
+
const textOnly = isRcsTextOnlyCardMediaType(card.mediaType);
|
|
309
|
+
const { rcsTitle, rcsDesc } = resolveRcsCardPreviewStrings(
|
|
310
|
+
card.title ?? '',
|
|
311
|
+
card.description ?? '',
|
|
312
|
+
merged,
|
|
313
|
+
isLibraryMode,
|
|
314
|
+
textOnly,
|
|
315
|
+
);
|
|
316
|
+
return {
|
|
317
|
+
...card,
|
|
318
|
+
title: rcsTitle,
|
|
319
|
+
description: rcsDesc,
|
|
320
|
+
};
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Before save: strip only legacy numeric self-placeholders (`{{1}}`, `{{2}}`, …) mistakenly stored as
|
|
326
|
+
* slot values. Preserve semantic tokens like `{{FirstName}}` from TagList — those are valid mappings.
|
|
327
|
+
*/
|
|
328
|
+
export function sanitizeCardVarMappedValue(val) {
|
|
329
|
+
if (val == null) return '';
|
|
330
|
+
const trimmedDisplayString = String(val).trim();
|
|
331
|
+
if (/^\{\{\d+\}\}$/.test(trimmedDisplayString)) return '';
|
|
332
|
+
return String(val);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Same completion rule as SmsTraiEdit RCS fallback — used by `isDisableDone` from
|
|
337
|
+
* `smsFallbackData.rcsSmsFallbackVarMapped` + template string.
|
|
338
|
+
* Every variable token (DLT {#…#} or mustache {{…}}) must have a non-empty trimmed value in the map.
|
|
339
|
+
*
|
|
340
|
+
* Slot keys are usually `${token}_${segmentIndex}` (same as VarSegmentMessageEditor). Persisted / API
|
|
341
|
+
* payloads may use `${token}_${varOrdinal}` with a 1-based occurrence index (see SmsTraiEdit init).
|
|
342
|
+
* We try segment index first, then ordinal — so e.g. template `{#var#}` (segment index 0) still matches
|
|
343
|
+
* map `{#var#}_1`.
|
|
344
|
+
*
|
|
345
|
+
* @param {string} templateText
|
|
346
|
+
* @param {Record<string, string>} [varSlotValueMap={}]
|
|
347
|
+
* @returns {boolean}
|
|
348
|
+
*/
|
|
349
|
+
export function areAllRcsSmsFallbackVarSlotsFilled(templateText, varSlotValueMap = {}) {
|
|
350
|
+
if (!templateText || typeof templateText !== 'string') return true;
|
|
351
|
+
const segments = splitTemplateVarString(templateText, COMBINED_SMS_TEMPLATE_VAR_REGEX);
|
|
352
|
+
const hasVarToken = segments.some(
|
|
353
|
+
(segment) =>
|
|
354
|
+
typeof segment === 'string'
|
|
355
|
+
&& isAnyTemplateVarToken(segment),
|
|
356
|
+
);
|
|
357
|
+
if (!hasVarToken) return true;
|
|
358
|
+
let varOrdinal = 0;
|
|
359
|
+
return segments.every((segment, segmentIndex) => {
|
|
360
|
+
if (
|
|
361
|
+
typeof segment !== 'string'
|
|
362
|
+
|| !isAnyTemplateVarToken(segment)
|
|
363
|
+
) return true;
|
|
364
|
+
varOrdinal += 1;
|
|
365
|
+
const indexKey = `${segment}_${segmentIndex}`;
|
|
366
|
+
const ordinalKey = `${segment}_${varOrdinal}`;
|
|
367
|
+
let mappedSlotValue;
|
|
368
|
+
if (Object.prototype.hasOwnProperty.call(varSlotValueMap, indexKey)) {
|
|
369
|
+
mappedSlotValue = varSlotValueMap[indexKey];
|
|
370
|
+
} else if (Object.prototype.hasOwnProperty.call(varSlotValueMap, ordinalKey)) {
|
|
371
|
+
mappedSlotValue = varSlotValueMap[ordinalKey];
|
|
372
|
+
} else {
|
|
373
|
+
mappedSlotValue = undefined;
|
|
374
|
+
}
|
|
375
|
+
if (mappedSlotValue == null) return false;
|
|
376
|
+
return String(mappedSlotValue).trim() !== '';
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
36
380
|
export const getRCSContent = (template) => {
|
|
37
381
|
const renderRcsSuggestionsPreview = (rcsSuggestions) => {
|
|
38
382
|
const renderArray = [];
|
|
@@ -84,8 +428,10 @@ export const getRCSContent = (template) => {
|
|
|
84
428
|
media = {},
|
|
85
429
|
description,
|
|
86
430
|
title,
|
|
431
|
+
mediaType,
|
|
87
432
|
suggestions = [],
|
|
88
433
|
} = cardContent[0];
|
|
434
|
+
const isTextOnlyCard = isRcsTextOnlyCardMediaType(mediaType);
|
|
89
435
|
const mediaPreview = media?.thumbnailUrl ? media.thumbnailUrl : media.mediaUrl;
|
|
90
436
|
return (
|
|
91
437
|
<div className="cap-rcs-creatives">
|
|
@@ -95,13 +441,15 @@ export const getRCSContent = (template) => {
|
|
|
95
441
|
className="rcs-listing-image"
|
|
96
442
|
/>
|
|
97
443
|
)}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
444
|
+
{!isTextOnlyCard && (
|
|
445
|
+
<CapLabel
|
|
446
|
+
type="label19"
|
|
447
|
+
className="rcs-listing-content title"
|
|
448
|
+
fontWeight="bold"
|
|
449
|
+
>
|
|
450
|
+
{title}
|
|
451
|
+
</CapLabel>
|
|
452
|
+
)}
|
|
105
453
|
<CapLabel type="label19" className="rcs-listing-content desc">
|
|
106
454
|
{description}
|
|
107
455
|
</CapLabel>
|
|
@@ -33,6 +33,10 @@ import injectReducer from '../../../utils/injectReducer';
|
|
|
33
33
|
import v2SmsCreateReducer from './reducer';
|
|
34
34
|
import * as globalActions from '../../Cap/actions';
|
|
35
35
|
import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox';
|
|
36
|
+
import {
|
|
37
|
+
getSmsEmbeddedFooterValidity,
|
|
38
|
+
getSmsMessageFromFormData,
|
|
39
|
+
} from '../smsFormDataHelpers';
|
|
36
40
|
|
|
37
41
|
export class Create extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
|
38
42
|
constructor(props) {
|
|
@@ -51,6 +55,7 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
51
55
|
modalContent: {title: "Alert", body: "Do you really want to delete this version?", type: 'confirm', id: 'sms-version-modal'},
|
|
52
56
|
showTestAndPreviewSlidebox: false,
|
|
53
57
|
isTestAndPreviewMode: false,
|
|
58
|
+
pendingGetFormData: false,
|
|
54
59
|
};
|
|
55
60
|
this.saveFormData = this.saveFormData.bind(this);
|
|
56
61
|
this.onFormDataChange = this.onFormDataChange.bind(this);
|
|
@@ -140,8 +145,9 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
140
145
|
}
|
|
141
146
|
|
|
142
147
|
componentWillReceiveProps(nextProps) {
|
|
143
|
-
|
|
144
|
-
|
|
148
|
+
// Only trigger on actual Done click (isGetFormData false -> true). Prevents auto-submit after user fixes brace error.
|
|
149
|
+
if (!nextProps.isFullMode && nextProps.isGetFormData && !this.props.isGetFormData) {
|
|
150
|
+
this.setState({ startValidation: true, pendingGetFormData: true });
|
|
145
151
|
} else if (nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
|
|
146
152
|
this.startValidation();
|
|
147
153
|
}
|
|
@@ -157,7 +163,9 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
157
163
|
layout: 'SMS',
|
|
158
164
|
type: 'TAG',
|
|
159
165
|
context: this.props.location.query.type === 'embedded' ? this.props.location.query.module : 'default',
|
|
160
|
-
embedded: this.props.
|
|
166
|
+
embedded: this.props.forceFullTagContext
|
|
167
|
+
? 'full'
|
|
168
|
+
: (this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full'),
|
|
161
169
|
};
|
|
162
170
|
if (this.props.getDefaultTags) {
|
|
163
171
|
query.context = this.props.getDefaultTags;
|
|
@@ -170,7 +178,20 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
170
178
|
}
|
|
171
179
|
}
|
|
172
180
|
|
|
181
|
+
componentDidUpdate() {
|
|
182
|
+
if (!this.props.embeddedSmsFallback || typeof this.props.onEmbeddedSmsFooterValidity !== 'function') {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// Report validity on every update. The reducer in SmsFallback bails out (returns same
|
|
186
|
+
// state reference) when the value is unchanged, so no re-render loop is triggered.
|
|
187
|
+
// Calling unconditionally handles both mutation-based and reference-based FormBuilder updates.
|
|
188
|
+
this.props.onEmbeddedSmsFooterValidity(getSmsEmbeddedFooterValidity(this.state.formData, this.state.tabCount));
|
|
189
|
+
}
|
|
190
|
+
|
|
173
191
|
componentWillUnmount() {
|
|
192
|
+
if (this.pendingGetFormDataTimeout) {
|
|
193
|
+
clearTimeout(this.pendingGetFormDataTimeout);
|
|
194
|
+
}
|
|
174
195
|
if (this.props.setIsLoadingContent) {
|
|
175
196
|
this.props.setIsLoadingContent(true); // setting isLoading of CreativesContainer so that slidebox foot can be hidden till content is loaded
|
|
176
197
|
}
|
|
@@ -199,6 +220,10 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
199
220
|
if (currentTab) {
|
|
200
221
|
this.setState({currentTab});
|
|
201
222
|
}
|
|
223
|
+
// Clear footer validation errors on input change so they refresh on next validation
|
|
224
|
+
if (this.props.showLiquidErrorInFooter) {
|
|
225
|
+
this.props.showLiquidErrorInFooter({ STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] });
|
|
226
|
+
}
|
|
202
227
|
}
|
|
203
228
|
|
|
204
229
|
onTagSelect(data, currentTab) {
|
|
@@ -266,6 +291,10 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
266
291
|
}
|
|
267
292
|
const result = {};
|
|
268
293
|
result.base = baseData;
|
|
294
|
+
/* Root field used by FormBuilder; embedded getFormSubscriptionData reads value.base */
|
|
295
|
+
if (formData['template-name'] !== undefined) {
|
|
296
|
+
result.base['template-name'] = formData['template-name'];
|
|
297
|
+
}
|
|
269
298
|
if (this.state.isValid) {
|
|
270
299
|
const msgObj = charCount.updateCharCount(baseData['sms-editor']);
|
|
271
300
|
result.base.msg_count = msgObj.msgCount;
|
|
@@ -349,7 +378,25 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
349
378
|
}
|
|
350
379
|
|
|
351
380
|
setFormValidity(isFormValid, errorData) {
|
|
352
|
-
this.setState({isFormValid, errorData})
|
|
381
|
+
this.setState({ isFormValid, errorData }, () => {
|
|
382
|
+
if (this.state.pendingGetFormData && !isFormValid) {
|
|
383
|
+
this.setState({ pendingGetFormData: false, startValidation: false });
|
|
384
|
+
// Reset parent's Done state so next Done click is a fresh attempt
|
|
385
|
+
if (this.props.onValidationFail) {
|
|
386
|
+
this.props.onValidationFail();
|
|
387
|
+
}
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
// In library mode with SMS, submit only when FormBuilder calls onSubmit (after liquid validation).
|
|
391
|
+
if (this.state.pendingGetFormData && this.props.getFormSubscriptionData && this.props.isFullMode) {
|
|
392
|
+
if (this.pendingGetFormDataTimeout) {
|
|
393
|
+
clearTimeout(this.pendingGetFormDataTimeout);
|
|
394
|
+
this.pendingGetFormDataTimeout = null;
|
|
395
|
+
}
|
|
396
|
+
this.props.getFormSubscriptionData(this.getFormData());
|
|
397
|
+
this.setState({ pendingGetFormData: false, startValidation: false });
|
|
398
|
+
}
|
|
399
|
+
});
|
|
353
400
|
}
|
|
354
401
|
|
|
355
402
|
injectMessages(elem) {
|
|
@@ -854,7 +901,9 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
854
901
|
layout: 'SMS',
|
|
855
902
|
type: 'TAG',
|
|
856
903
|
context: (data && data.toLowerCase() === 'all') ? 'default' : (data && data.toLowerCase()),
|
|
857
|
-
embedded: this.props.
|
|
904
|
+
embedded: this.props.forceFullTagContext
|
|
905
|
+
? 'full'
|
|
906
|
+
: (this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full'),
|
|
858
907
|
};
|
|
859
908
|
this.props.globalActions.fetchSchemaForEntity(query);
|
|
860
909
|
}
|
|
@@ -862,10 +911,22 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
862
911
|
removeStandAlone() {
|
|
863
912
|
const schema = _.cloneDeep(this.state.schema);
|
|
864
913
|
const childSections = _.get(schema, 'standalone.sections[0].childSections');
|
|
914
|
+
if (!childSections || childSections.length <= 2) {
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
/* In-form Save / Test row removed for embedded SMS; slidebox footer (SlideBoxFooter) provides actions — see CreativesContainer. */
|
|
865
918
|
childSections.splice(2, 1);
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
919
|
+
/*
|
|
920
|
+
* Creatives library also drops the standalone template-name block because `SlideBoxHeader` shows the name.
|
|
921
|
+
* RCS SMS fallback uses the same slidebox footer but no template-name header — keep Creative name in the form.
|
|
922
|
+
*/
|
|
923
|
+
if (!this.props.embeddedSmsFallback) {
|
|
924
|
+
const fields = _.get(childSections, '[1].childSections[0].childSections');
|
|
925
|
+
if (fields && fields.length > 0) {
|
|
926
|
+
fields.splice(0, 1);
|
|
927
|
+
_.set(childSections, '[1].childSections[0].childSections', fields);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
869
930
|
_.set(schema, 'standalone.sections[0].childSections', childSections);
|
|
870
931
|
this.setState({ schema, loadingStatus: this.state.loadingStatus + 1 });
|
|
871
932
|
}
|
|
@@ -911,37 +972,8 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
911
972
|
this.setState({startValidation: false});
|
|
912
973
|
}
|
|
913
974
|
|
|
914
|
-
getTemplateContent = () =>
|
|
915
|
-
|
|
916
|
-
if (!this.state.formData || !Array.isArray(this.state.formData) || this.state.formData.length === 0) {
|
|
917
|
-
return '';
|
|
918
|
-
}
|
|
919
|
-
const currentTabData = this.state.formData[this.state.currentTab - 1];
|
|
920
|
-
if (!currentTabData) {
|
|
921
|
-
return '';
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
// PRIORITY 1: Check direct path first (most common for SMS)
|
|
925
|
-
// This handles: formData[0]['sms-editor']
|
|
926
|
-
if (currentTabData['sms-editor']) {
|
|
927
|
-
return currentTabData['sms-editor'];
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
// PRIORITY 2: Check activeTab structure (for versioned templates)
|
|
931
|
-
// This handles: formData[0][activeTab]['sms-editor']
|
|
932
|
-
const activeTab = currentTabData?.activeTab || 'base';
|
|
933
|
-
if (currentTabData[activeTab]?.['sms-editor']) {
|
|
934
|
-
return currentTabData[activeTab]['sms-editor'];
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
// PRIORITY 3: Check base explicitly (fallback)
|
|
938
|
-
// This handles: formData[0]['base']['sms-editor']
|
|
939
|
-
if (currentTabData['base']?.['sms-editor']) {
|
|
940
|
-
return currentTabData['base']['sms-editor'];
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
return '';
|
|
944
|
-
}
|
|
975
|
+
getTemplateContent = () =>
|
|
976
|
+
getSmsMessageFromFormData(this.state.formData, this.state.currentTab);
|
|
945
977
|
|
|
946
978
|
handleTestAndPreview = () => {
|
|
947
979
|
// If parent is managing state (props exist), call parent handler
|
|
@@ -970,6 +1002,44 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
970
1002
|
}
|
|
971
1003
|
|
|
972
1004
|
saveFormData() {
|
|
1005
|
+
/*
|
|
1006
|
+
* RCS SMS fallback slidebox (embeddedSmsFallback): parent passes isFullMode from RCS, but we must not
|
|
1007
|
+
* call createTemplate — that spins CapSpin on createTemplateInProgress and is not the embedded contract.
|
|
1008
|
+
* Same as library: hand off form payload via getFormSubscriptionData.
|
|
1009
|
+
*/
|
|
1010
|
+
if (this.props.embeddedSmsFallback && this.props.getFormSubscriptionData) {
|
|
1011
|
+
const { isTemplateNameEmpty, isMessageEmpty } = getSmsEmbeddedFooterValidity(
|
|
1012
|
+
this.state.formData,
|
|
1013
|
+
this.state.tabCount,
|
|
1014
|
+
);
|
|
1015
|
+
if (isTemplateNameEmpty || isMessageEmpty) {
|
|
1016
|
+
this.setState({ startValidation: true, pendingGetFormData: false });
|
|
1017
|
+
if (this.props.onValidationFail) {
|
|
1018
|
+
this.props.onValidationFail();
|
|
1019
|
+
}
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const payload = this.getFormData();
|
|
1023
|
+
if (!payload.validity) {
|
|
1024
|
+
if (this.props.onValidationFail) {
|
|
1025
|
+
this.props.onValidationFail();
|
|
1026
|
+
}
|
|
1027
|
+
this.setState({ pendingGetFormData: false, startValidation: false });
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
this.props.getFormSubscriptionData(payload);
|
|
1031
|
+
this.setState({ pendingGetFormData: false, startValidation: false });
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
// In library mode: FormBuilder calls onSubmit only after liquid validation succeeds.
|
|
1035
|
+
// Submit to parent here so the slidebox can close with valid data.
|
|
1036
|
+
if (!this.props.isFullMode) {
|
|
1037
|
+
if (this.state.pendingGetFormData && this.props.getFormSubscriptionData) {
|
|
1038
|
+
this.props.getFormSubscriptionData(this.getFormData());
|
|
1039
|
+
this.setState({ pendingGetFormData: false, startValidation: false });
|
|
1040
|
+
}
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
973
1043
|
//Logic to save in db etc
|
|
974
1044
|
const formData = _.cloneDeep(this.state.formData);
|
|
975
1045
|
const obj = {};
|
|
@@ -1060,6 +1130,10 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
1060
1130
|
onTestContentClicked={this.props.onTestContentClicked}
|
|
1061
1131
|
onPreviewContentClicked={this.props.onPreviewContentClicked}
|
|
1062
1132
|
eventContextTags={this.props?.eventContextTags}
|
|
1133
|
+
waitEventContextTags={this.props?.waitEventContextTags}
|
|
1134
|
+
tagListGetPopupContainer={this.props.tagListGetPopupContainer}
|
|
1135
|
+
tagListPopoverOverlayStyle={this.props.tagListPopoverOverlayStyle}
|
|
1136
|
+
tagListPopoverOverlayClassName={this.props.tagListPopoverOverlayClassName}
|
|
1063
1137
|
/>
|
|
1064
1138
|
</CapColumn>
|
|
1065
1139
|
</CapRow>
|
|
@@ -1072,6 +1146,7 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
1072
1146
|
formData={this.state.formData}
|
|
1073
1147
|
content={this.getTemplateContent()}
|
|
1074
1148
|
currentChannel={SMS}
|
|
1149
|
+
smsRegister={this.props.smsRegister}
|
|
1075
1150
|
/>
|
|
1076
1151
|
</div>
|
|
1077
1152
|
);
|
|
@@ -1096,10 +1171,18 @@ Create.propTypes = {
|
|
|
1096
1171
|
isLoadingMetaEntities: PropTypes.bool,
|
|
1097
1172
|
selectedOfferDetails: PropTypes.array,
|
|
1098
1173
|
eventContextTags: PropTypes.array,
|
|
1174
|
+
waitEventContextTags: PropTypes.object,
|
|
1099
1175
|
showTestAndPreviewSlidebox: PropTypes.bool,
|
|
1100
1176
|
handleTestAndPreview: PropTypes.func,
|
|
1101
1177
|
handleCloseTestAndPreview: PropTypes.func,
|
|
1102
1178
|
isTestAndPreviewMode: PropTypes.bool,
|
|
1179
|
+
smsRegister: PropTypes.any,
|
|
1180
|
+
forceFullTagContext: PropTypes.bool,
|
|
1181
|
+
embeddedSmsFallback: PropTypes.bool,
|
|
1182
|
+
onEmbeddedSmsFooterValidity: PropTypes.func,
|
|
1183
|
+
tagListGetPopupContainer: PropTypes.func,
|
|
1184
|
+
tagListPopoverOverlayStyle: PropTypes.object,
|
|
1185
|
+
tagListPopoverOverlayClassName: PropTypes.string,
|
|
1103
1186
|
};
|
|
1104
1187
|
|
|
1105
1188
|
const mapStateToProps = createStructuredSelector({
|
|
@@ -90,6 +90,10 @@ export default defineMessages({
|
|
|
90
90
|
id: 'creatives.containersV2.Create.validationError',
|
|
91
91
|
defaultMessage: 'Validation error',
|
|
92
92
|
},
|
|
93
|
+
"unbalancedCurlyBraces": {
|
|
94
|
+
id: 'creatives.containersV2.Create.unbalancedCurlyBraces',
|
|
95
|
+
defaultMessage: 'Please close all curly braces in the message.',
|
|
96
|
+
},
|
|
93
97
|
"smsTemplateCreatedSuccess": {
|
|
94
98
|
id: 'creatives.containersV2.Create.smsTemplateCreatedSuccess',
|
|
95
99
|
defaultMessage: 'SMS Template Created Successfully',
|