@capillarytech/creatives-library 8.0.319 → 8.0.320
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constants/unified.js +14 -0
- package/package.json +1 -1
- package/utils/templateVarUtils.js +172 -0
- package/utils/tests/templateVarUtils.test.js +160 -0
- package/v2Components/CapTagList/index.js +10 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +11 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +12 -0
- package/v2Components/CommonTestAndPreview/constants.js +38 -0
- package/v2Components/CommonTestAndPreview/index.js +693 -155
- package/v2Components/CommonTestAndPreview/messages.js +41 -3
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/v2Components/CommonTestAndPreview/sagas.js +15 -6
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +269 -1
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +25 -4
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -4
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
- package/v2Components/FormBuilder/index.js +7 -1
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
- package/v2Components/SmsFallback/constants.js +73 -0
- package/v2Components/SmsFallback/index.js +956 -0
- package/v2Components/SmsFallback/index.scss +265 -0
- package/v2Components/SmsFallback/messages.js +78 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +8 -1
- package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
- package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
- package/v2Components/VarSegmentMessageEditor/index.js +125 -0
- package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
- package/v2Containers/CreativesContainer/constants.js +9 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
- package/v2Containers/CreativesContainer/index.js +289 -99
- package/v2Containers/CreativesContainer/index.scss +51 -1
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +104 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -10
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
- package/v2Containers/Rcs/constants.js +32 -1
- package/v2Containers/Rcs/index.js +950 -873
- package/v2Containers/Rcs/index.scss +85 -6
- package/v2Containers/Rcs/messages.js +10 -1
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +40834 -1963
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
- package/v2Containers/Rcs/tests/index.test.js +41 -38
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
- package/v2Containers/Rcs/tests/utils.test.js +379 -1
- package/v2Containers/Rcs/utils.js +358 -10
- package/v2Containers/Sms/Create/index.js +81 -36
- package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/v2Containers/SmsTrai/Create/index.js +9 -4
- package/v2Containers/SmsTrai/Edit/constants.js +2 -0
- package/v2Containers/SmsTrai/Edit/index.js +609 -128
- package/v2Containers/SmsTrai/Edit/index.scss +121 -0
- package/v2Containers/SmsTrai/Edit/messages.js +9 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4327 -2374
- package/v2Containers/SmsWrapper/index.js +37 -8
- package/v2Containers/TagList/index.js +6 -0
- package/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/v2Containers/Templates/_templates.scss +61 -2
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +90 -40
- package/v2Containers/Templates/sagas.js +57 -12
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
- package/v2Containers/Templates/tests/sagas.test.js +193 -12
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
- package/v2Containers/TemplatesV2/index.js +86 -23
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/v2Containers/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared logic for CreativesContainer slidebox + embedded flows (e.g. RCS SMS fallback)
|
|
3
|
+
* that mirror the same footer liquid errors and layout margins.
|
|
4
|
+
*/
|
|
5
|
+
import get from 'lodash/get';
|
|
6
|
+
import {
|
|
7
|
+
CAP_SPACE_32,
|
|
8
|
+
CAP_SPACE_56,
|
|
9
|
+
CAP_SPACE_64,
|
|
10
|
+
} from '@capillarytech/cap-ui-library/styled/variables';
|
|
11
|
+
import * as constants from './constants';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns true if value is "deep empty": no errors present.
|
|
15
|
+
* Same rules as CreativesContainer (used for liquid / standard error payloads).
|
|
16
|
+
*/
|
|
17
|
+
export function isDeepEmpty(value) {
|
|
18
|
+
if (value == null) return true;
|
|
19
|
+
if (typeof value === 'string') return value.length === 0;
|
|
20
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
21
|
+
if (typeof value === 'object') {
|
|
22
|
+
return Object.values(value).every(isDeepEmpty);
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Header/content margin below slidebox chrome when ErrorInfoNote stacks errors — same formula as CreativesContainer#render.
|
|
29
|
+
*/
|
|
30
|
+
export function getSlideBoxWrapperMarginFromLiquidErrors(liquidErrorMessage) {
|
|
31
|
+
return (get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0
|
|
32
|
+
&& get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0)
|
|
33
|
+
? CAP_SPACE_64
|
|
34
|
+
: get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0
|
|
35
|
+
? CAP_SPACE_56
|
|
36
|
+
: get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0
|
|
37
|
+
? CAP_SPACE_32
|
|
38
|
+
: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Maps FormBuilder `showLiquidErrorInFooter` args to slidebox footer state.
|
|
43
|
+
* Returns `null` when CreativesContainer intentionally skips updating (Mobile Push OLD empty clear).
|
|
44
|
+
*/
|
|
45
|
+
export function computeLiquidFooterUpdateFromFormBuilder(
|
|
46
|
+
errorMessagesFromFormBuilder,
|
|
47
|
+
currentFormBuilderTab,
|
|
48
|
+
{ previousIsLiquidValidationError, currentChannelUpper } = {},
|
|
49
|
+
) {
|
|
50
|
+
const liquidMsgs = get(errorMessagesFromFormBuilder, constants.LIQUID_ERROR_MSG, []);
|
|
51
|
+
const standardMsgs = get(errorMessagesFromFormBuilder, constants.STANDARD_ERROR_MSG, []);
|
|
52
|
+
const hasLiquid = !isDeepEmpty(liquidMsgs);
|
|
53
|
+
const hasStandard = !isDeepEmpty(standardMsgs);
|
|
54
|
+
const isLiquidValidationError = hasLiquid || hasStandard;
|
|
55
|
+
const isMobilePush = currentChannelUpper === constants.MOBILE_PUSH;
|
|
56
|
+
if (!hasLiquid && !hasStandard && previousIsLiquidValidationError && isMobilePush) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
isLiquidValidationError,
|
|
61
|
+
liquidErrorMessage: errorMessagesFromFormBuilder,
|
|
62
|
+
activeFormBuilderTab:
|
|
63
|
+
currentFormBuilderTab === 1
|
|
64
|
+
? constants.ANDROID
|
|
65
|
+
: (currentFormBuilderTab === 2 ? constants.IOS : null),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import {
|
|
4
|
-
CAP_SPACE_16, CAP_SPACE_32, CAP_SPACE_56, CAP_SPACE_64,
|
|
5
|
-
} from '@capillarytech/cap-ui-library/styled/variables';
|
|
6
|
-
|
|
7
3
|
import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
|
|
8
4
|
import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
|
|
9
5
|
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
@@ -13,12 +9,11 @@ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
|
|
|
13
9
|
import { injectIntl, FormattedMessage } from 'react-intl';
|
|
14
10
|
import classnames from 'classnames';
|
|
15
11
|
import {
|
|
16
|
-
isEmpty, get, forEach, cloneDeep, debounce,
|
|
12
|
+
isEmpty, get, forEach, cloneDeep, debounce, pick,
|
|
17
13
|
} from 'lodash';
|
|
18
14
|
import { connect } from 'react-redux';
|
|
19
15
|
import { createStructuredSelector } from 'reselect';
|
|
20
16
|
import { bindActionCreators, compose } from 'redux';
|
|
21
|
-
import styled from 'styled-components';
|
|
22
17
|
import { GA } from '@capillarytech/cap-ui-utils';
|
|
23
18
|
import { DAEMON } from '@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes';
|
|
24
19
|
|
|
@@ -47,6 +42,8 @@ import {
|
|
|
47
42
|
import {EXTERNAL_URL, SITE_URL, WEBPUSH_MEDIA_TYPES} from '../WebPush/constants';
|
|
48
43
|
import { IMAGE, VIDEO } from '../Facebook/Advertisement/constant';
|
|
49
44
|
import { RCS_STATUSES } from '../Rcs/constants';
|
|
45
|
+
import { mapRcsCardContentForConsumerWithResolvedTags } from '../Rcs/utils';
|
|
46
|
+
import { RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
|
|
50
47
|
import { CREATIVE } from '../Facebook/constants';
|
|
51
48
|
import { LOYALTY } from '../App/constants';
|
|
52
49
|
import {
|
|
@@ -61,6 +58,11 @@ import { capSagaForFetchSchemaForEntity, capSagaLiquidEntity } from '../Cap/saga
|
|
|
61
58
|
import { v2TemplateSagaWatchGetDefaultBeeTemplates } from '../Templates/sagas';
|
|
62
59
|
import { DYNAMIC_URL } from '../../v2Components/CapWhatsappCTA/constants';
|
|
63
60
|
import ErrorInfoNote from '../../v2Components/ErrorInfoNote';
|
|
61
|
+
import SlideBoxWrapper from './CreativesSlideBoxWrapper';
|
|
62
|
+
import {
|
|
63
|
+
computeLiquidFooterUpdateFromFormBuilder,
|
|
64
|
+
getSlideBoxWrapperMarginFromLiquidErrors,
|
|
65
|
+
} from './embeddedSlideboxUtils';
|
|
64
66
|
|
|
65
67
|
import {
|
|
66
68
|
transformChannelPayload,
|
|
@@ -69,51 +71,24 @@ import {
|
|
|
69
71
|
import { MANUAL_CAROUSEL } from '../MobilePushNew/constants';
|
|
70
72
|
import { BIG_HTML } from '../InApp/constants';
|
|
71
73
|
|
|
72
|
-
/**
|
|
73
|
-
* Returns true if value is "deep empty": no errors present.
|
|
74
|
-
* - null/undefined: empty
|
|
75
|
-
* - string: empty if length === 0
|
|
76
|
-
* - array: empty if length === 0
|
|
77
|
-
* - plain object (e.g. { android: [], ios: [], generic: [] }): empty only if every value is deep-empty
|
|
78
|
-
*/
|
|
79
|
-
function isDeepEmpty(value) {
|
|
80
|
-
if (value == null) return true;
|
|
81
|
-
if (typeof value === 'string') return value.length === 0;
|
|
82
|
-
if (Array.isArray(value)) return value.length === 0;
|
|
83
|
-
if (typeof value === 'object') {
|
|
84
|
-
return Object.values(value).every(isDeepEmpty);
|
|
85
|
-
}
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
74
|
const classPrefix = 'add-creatives-section';
|
|
90
75
|
const CREATIVES_CONTAINER = 'creativesContainer';
|
|
91
76
|
|
|
92
|
-
const SlideBoxWrapper = styled.div`
|
|
93
|
-
.cap-slide-box-v2-container{
|
|
94
|
-
.slidebox-header, .slidebox-content-container{
|
|
95
|
-
margin-bottom: ${({ slideBoxWrapperMargin }) => `${slideBoxWrapperMargin}`};
|
|
96
|
-
padding: 0 rem;
|
|
97
|
-
&.has-footer{
|
|
98
|
-
overflow-x: hidden;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
.slidebox-footer{
|
|
102
|
-
/* Only apply margin-bottom to footer when ErrorInfoNote is shown in footer (BEE editor) */
|
|
103
|
-
/* For HTML Editor, errors are shown in ValidationErrorDisplay (inside content area), so no footer margin needed */
|
|
104
|
-
margin-bottom: ${({ shouldApplyFooterMargin }) => (shouldApplyFooterMargin ? `${CAP_SPACE_16}` : '0')};
|
|
105
|
-
padding: 0 rem;
|
|
106
|
-
&.has-footer{
|
|
107
|
-
overflow-x: hidden;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
`;
|
|
112
77
|
export class Creatives extends React.Component {
|
|
113
78
|
constructor(props) {
|
|
114
79
|
super(props);
|
|
115
80
|
|
|
116
|
-
const
|
|
81
|
+
const useLocalTemplates = get(
|
|
82
|
+
props,
|
|
83
|
+
'localTemplatesConfig.useLocalTemplates',
|
|
84
|
+
get(props, 'useLocalTemplates', false),
|
|
85
|
+
);
|
|
86
|
+
const initialSlidBoxContent = this.getSlideBoxContent({
|
|
87
|
+
mode: props.creativesMode,
|
|
88
|
+
templateData: props.templateData,
|
|
89
|
+
isFullMode: props.isFullMode,
|
|
90
|
+
useLocalTemplates,
|
|
91
|
+
});
|
|
117
92
|
|
|
118
93
|
this.state = {
|
|
119
94
|
isLoadingContent: true,
|
|
@@ -160,7 +135,13 @@ export class Creatives extends React.Component {
|
|
|
160
135
|
}
|
|
161
136
|
|
|
162
137
|
componentWillUnmount() {
|
|
163
|
-
|
|
138
|
+
const isEmbedded = get(this.props, 'location.query.type', '') === "embedded";
|
|
139
|
+
const useLocalTemplates = get(
|
|
140
|
+
this.props,
|
|
141
|
+
'localTemplatesConfig.useLocalTemplates',
|
|
142
|
+
get(this.props, 'useLocalTemplates', false),
|
|
143
|
+
);
|
|
144
|
+
if (isEmbedded && !useLocalTemplates) {
|
|
164
145
|
this.props.templateActions.resetTemplateStoreData();
|
|
165
146
|
}
|
|
166
147
|
this.props.globalActions.clearMetaEntities();
|
|
@@ -763,14 +744,41 @@ export class Creatives extends React.Component {
|
|
|
763
744
|
creativeName = "",
|
|
764
745
|
channel = constants.RCS,
|
|
765
746
|
accountId = "",
|
|
747
|
+
rcsCardVarMapped,
|
|
766
748
|
} = templateData || {};
|
|
767
|
-
const
|
|
749
|
+
const { isFullMode: isFullModeForRcsPayload } = this.props;
|
|
750
|
+
const firstCardIn = (rcsContent.cardContent && rcsContent.cardContent[0]) || {};
|
|
751
|
+
const {
|
|
752
|
+
cardDisplayTitle: _omitDispTitleIn,
|
|
753
|
+
cardDisplayDescription: _omitDispDescIn,
|
|
754
|
+
...cardContent
|
|
755
|
+
} = firstCardIn;
|
|
768
756
|
const Status = RCS_STATUSES.approved || '';
|
|
757
|
+
const mergedCardVarMapped = (() => {
|
|
758
|
+
const nestedCardVarMapped = cardContent?.cardVarMapped;
|
|
759
|
+
const rootMirrorCardVarMapped = rcsCardVarMapped;
|
|
760
|
+
const nestedRecord =
|
|
761
|
+
nestedCardVarMapped != null && typeof nestedCardVarMapped === 'object'
|
|
762
|
+
? nestedCardVarMapped
|
|
763
|
+
: {};
|
|
764
|
+
const rootRecord =
|
|
765
|
+
rootMirrorCardVarMapped != null && typeof rootMirrorCardVarMapped === 'object'
|
|
766
|
+
? rootMirrorCardVarMapped
|
|
767
|
+
: {};
|
|
768
|
+
const mergedFromRootAndNested = { ...rootRecord, ...nestedRecord };
|
|
769
|
+
return Object.keys(mergedFromRootAndNested).length > 0 ? mergedFromRootAndNested : null;
|
|
770
|
+
})();
|
|
771
|
+
// Campaigns (embedded): do not duplicate `cardVarMapped` as root `rcsCardVarMapped` on send —
|
|
772
|
+
// slot map stays on `versions…cardContent[0].cardVarMapped` only. Library full mode keeps root mirror.
|
|
773
|
+
// Use `=== true` so omitted/undefined `isFullMode` does not behave like library (avoids duplicate on approval payload).
|
|
774
|
+
const includeRootRcsCardVarMapped =
|
|
775
|
+
mergedCardVarMapped && isFullModeForRcsPayload === true;
|
|
769
776
|
|
|
770
777
|
creativesTemplateData = {
|
|
771
778
|
type: channel,
|
|
772
779
|
edit: true,
|
|
773
780
|
name: creativeName,
|
|
781
|
+
...(includeRootRcsCardVarMapped ? { rcsCardVarMapped: mergedCardVarMapped } : {}),
|
|
774
782
|
versions: {
|
|
775
783
|
base: {
|
|
776
784
|
content: {
|
|
@@ -781,6 +789,7 @@ export class Creatives extends React.Component {
|
|
|
781
789
|
cardContent: [
|
|
782
790
|
{
|
|
783
791
|
...cardContent,
|
|
792
|
+
...(mergedCardVarMapped ? { cardVarMapped: mergedCardVarMapped } : {}),
|
|
784
793
|
Status,
|
|
785
794
|
},
|
|
786
795
|
],
|
|
@@ -931,7 +940,10 @@ export class Creatives extends React.Component {
|
|
|
931
940
|
return newExpandableDetails;
|
|
932
941
|
}
|
|
933
942
|
|
|
934
|
-
getCreativesData = async (
|
|
943
|
+
getCreativesData = async (channelParam, template, templateRecords) => { //from creatives to consumers
|
|
944
|
+
const channel = String(
|
|
945
|
+
channelParam || template?.type || get(template, 'value.type') || '',
|
|
946
|
+
).toUpperCase();
|
|
935
947
|
let templateData = { channel };
|
|
936
948
|
switch (channel) {
|
|
937
949
|
case constants.SMS:
|
|
@@ -1225,7 +1237,7 @@ export class Creatives extends React.Component {
|
|
|
1225
1237
|
};
|
|
1226
1238
|
}
|
|
1227
1239
|
break;
|
|
1228
|
-
case constants.FACEBOOK:
|
|
1240
|
+
case constants.FACEBOOK:
|
|
1229
1241
|
if (template.value) {
|
|
1230
1242
|
const FacebookAd = template?.value?.versions?.base?.content?.FacebookAd;
|
|
1231
1243
|
const { type } = FacebookAd[0];
|
|
@@ -1269,36 +1281,109 @@ export class Creatives extends React.Component {
|
|
|
1269
1281
|
selectedMarketingObjective: template.value.selectedMarketingObjective,
|
|
1270
1282
|
};
|
|
1271
1283
|
}
|
|
1272
|
-
}
|
|
1273
1284
|
break;
|
|
1274
|
-
case constants.RCS:
|
|
1285
|
+
case constants.RCS:
|
|
1275
1286
|
if (template.value) {
|
|
1276
|
-
const {
|
|
1277
|
-
} = template.value || {};
|
|
1278
|
-
const
|
|
1287
|
+
const { isFullMode: isFullModeForRcsConsumerPayload } = this.props;
|
|
1288
|
+
const { name = "", versions = {} } = template.value || {};
|
|
1289
|
+
const fromSubmit = get(versions, 'base.content.RCS.smsFallBackContent', {});
|
|
1290
|
+
const fromRecords = {
|
|
1291
|
+
...(templateRecords?.smsFallBackContent || {}),
|
|
1292
|
+
...(get(templateRecords, 'versions.base.content.RCS.smsFallBackContent') || {}),
|
|
1293
|
+
};
|
|
1294
|
+
const hasMeaningfulRcsSmsFallback = (smsFallbackPayload) => {
|
|
1295
|
+
if (
|
|
1296
|
+
!smsFallbackPayload
|
|
1297
|
+
|| typeof smsFallbackPayload !== 'object'
|
|
1298
|
+
|| Object.keys(smsFallbackPayload).length === 0
|
|
1299
|
+
) {
|
|
1300
|
+
return false;
|
|
1301
|
+
}
|
|
1302
|
+
const fallbackBodyText = String(
|
|
1303
|
+
smsFallbackPayload.smsContent
|
|
1304
|
+
?? smsFallbackPayload.smsTemplateContent
|
|
1305
|
+
?? smsFallbackPayload.message
|
|
1306
|
+
?? smsFallbackPayload.content
|
|
1307
|
+
?? '',
|
|
1308
|
+
).trim();
|
|
1309
|
+
const fallbackTemplateName = String(
|
|
1310
|
+
smsFallbackPayload.smsTemplateName ?? smsFallbackPayload.templateName ?? '',
|
|
1311
|
+
).trim();
|
|
1312
|
+
const rcsSmsFallbackVarMapped =
|
|
1313
|
+
smsFallbackPayload?.[RCS_SMS_FALLBACK_VAR_MAPPED_PROP];
|
|
1314
|
+
const hasVarMappedEntries =
|
|
1315
|
+
rcsSmsFallbackVarMapped != null
|
|
1316
|
+
&& typeof rcsSmsFallbackVarMapped === 'object'
|
|
1317
|
+
&& Object.keys(rcsSmsFallbackVarMapped).length > 0;
|
|
1318
|
+
return (
|
|
1319
|
+
fallbackBodyText !== ''
|
|
1320
|
+
|| fallbackTemplateName !== ''
|
|
1321
|
+
|| hasVarMappedEntries
|
|
1322
|
+
);
|
|
1323
|
+
};
|
|
1324
|
+
// If submit has only empty strings, do not let it wipe fallback mirrored on templateRecords (library round-trip).
|
|
1325
|
+
const smsFallBackContent = hasMeaningfulRcsSmsFallback(fromSubmit)
|
|
1326
|
+
? { ...fromRecords, ...fromSubmit }
|
|
1327
|
+
: { ...fromSubmit, ...fromRecords };
|
|
1279
1328
|
const {
|
|
1280
|
-
cardContent = [],
|
|
1329
|
+
cardContent: cardContentFromSubmit = [],
|
|
1281
1330
|
contentType = "",
|
|
1282
1331
|
cardType = "",
|
|
1283
1332
|
cardSettings = {},
|
|
1284
1333
|
accountId = "",
|
|
1285
1334
|
} = get(versions, 'base.content.RCS.rcsContent', {});
|
|
1335
|
+
const rootRcsCardVarMappedFromSubmit = get(template, 'value.rcsCardVarMapped');
|
|
1336
|
+
const firstCardFromSubmit = Array.isArray(cardContentFromSubmit)
|
|
1337
|
+
? cardContentFromSubmit[0]
|
|
1338
|
+
: null;
|
|
1339
|
+
const cardContent = mapRcsCardContentForConsumerWithResolvedTags(
|
|
1340
|
+
cardContentFromSubmit,
|
|
1341
|
+
rootRcsCardVarMappedFromSubmit,
|
|
1342
|
+
isFullModeForRcsConsumerPayload,
|
|
1343
|
+
);
|
|
1286
1344
|
const rcsContent = {
|
|
1287
1345
|
contentType,
|
|
1288
1346
|
cardType,
|
|
1289
1347
|
cardSettings,
|
|
1290
1348
|
cardContent,
|
|
1291
1349
|
};
|
|
1350
|
+
const cardVarMappedFromFirstRcsCard =
|
|
1351
|
+
firstCardFromSubmit?.cardVarMapped != null
|
|
1352
|
+
&& typeof firstCardFromSubmit.cardVarMapped === 'object'
|
|
1353
|
+
? firstCardFromSubmit.cardVarMapped
|
|
1354
|
+
: null;
|
|
1355
|
+
const includeRootRcsCardVarMappedOnConsumerPayload =
|
|
1356
|
+
cardVarMappedFromFirstRcsCard
|
|
1357
|
+
&& Object.keys(cardVarMappedFromFirstRcsCard).length > 0
|
|
1358
|
+
&& isFullModeForRcsConsumerPayload === true;
|
|
1292
1359
|
templateData = {
|
|
1293
1360
|
channel,
|
|
1294
1361
|
creativeName: name,
|
|
1295
1362
|
rcsContent,
|
|
1296
1363
|
accountId: accountId,
|
|
1364
|
+
...(includeRootRcsCardVarMappedOnConsumerPayload
|
|
1365
|
+
? { rcsCardVarMapped: cardVarMappedFromFirstRcsCard }
|
|
1366
|
+
: {}),
|
|
1297
1367
|
};
|
|
1368
|
+
// Library / campaign consumers round-trip templateData via getTemplateData; include SMS fallback
|
|
1369
|
+
// so reopening the editor restores fallback text and tag mappings.
|
|
1370
|
+
// cap-campaigns-v2 API expects `smsFallBackContent.message` (see normalizeRcsMessageContentForApi).
|
|
1371
|
+
if (hasMeaningfulRcsSmsFallback(smsFallBackContent)) {
|
|
1372
|
+
const smsText =
|
|
1373
|
+
smsFallBackContent.message
|
|
1374
|
+
?? smsFallBackContent.smsContent
|
|
1375
|
+
?? smsFallBackContent.smsTemplateContent
|
|
1376
|
+
?? '';
|
|
1377
|
+
templateData.smsFallBackContent = {
|
|
1378
|
+
...smsFallBackContent,
|
|
1379
|
+
...(String(smsText).trim() !== ''
|
|
1380
|
+
? { message: String(smsText).trim() }
|
|
1381
|
+
: {}),
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1298
1384
|
}
|
|
1299
|
-
}
|
|
1300
1385
|
break;
|
|
1301
|
-
case constants.ZALO:
|
|
1386
|
+
case constants.ZALO:
|
|
1302
1387
|
if (template.value) {
|
|
1303
1388
|
templateData = {
|
|
1304
1389
|
...template.value,
|
|
@@ -1307,7 +1392,6 @@ export class Creatives extends React.Component {
|
|
|
1307
1392
|
delete templateData.type;
|
|
1308
1393
|
}
|
|
1309
1394
|
}
|
|
1310
|
-
}
|
|
1311
1395
|
break;
|
|
1312
1396
|
case constants.WEBPUSH: {
|
|
1313
1397
|
if (template.value) {
|
|
@@ -1414,7 +1498,10 @@ export class Creatives extends React.Component {
|
|
|
1414
1498
|
return templateData;
|
|
1415
1499
|
};
|
|
1416
1500
|
|
|
1417
|
-
getSlideBoxContent({ mode, templateData, isFullMode }) {
|
|
1501
|
+
getSlideBoxContent({ mode, templateData, isFullMode, useLocalTemplates }) {
|
|
1502
|
+
if (useLocalTemplates && mode === 'create' && isEmpty(templateData)) {
|
|
1503
|
+
return 'templates';
|
|
1504
|
+
}
|
|
1418
1505
|
let creativesMode = isFullMode ? 'createTemplate' : 'templates';// for library mode templates page is initial mode and for full mode createTemplates
|
|
1419
1506
|
if (mode === 'create' && isFullMode) {
|
|
1420
1507
|
creativesMode = 'createTemplate';
|
|
@@ -1502,24 +1589,110 @@ export class Creatives extends React.Component {
|
|
|
1502
1589
|
getFormData = (template) => {
|
|
1503
1590
|
// Always reset isGetFormData so the child does not re-send form data on every re-render
|
|
1504
1591
|
// (e.g. when user fixes validation error by typing, we must not auto-close the slidebox)
|
|
1505
|
-
this.setState(
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
{
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1592
|
+
this.setState(
|
|
1593
|
+
(prevState) => {
|
|
1594
|
+
const next = { isGetFormData: false };
|
|
1595
|
+
if (!template.validity) {
|
|
1596
|
+
return next;
|
|
1597
|
+
}
|
|
1598
|
+
const baseTd = prevState.templateData || template;
|
|
1599
|
+
const channel = (
|
|
1600
|
+
baseTd?.type
|
|
1601
|
+
|| template?.type
|
|
1602
|
+
|| get(template, 'value.type')
|
|
1603
|
+
|| ''
|
|
1604
|
+
).toUpperCase();
|
|
1605
|
+
// Library mode: persist last submitted creatives shape so reopening still hydrates the editor
|
|
1606
|
+
// (parent may not merge getCreativesData back into templateData).
|
|
1607
|
+
if (this.props.isFullMode === false && template.value) {
|
|
1608
|
+
if (channel === constants.RCS) {
|
|
1609
|
+
const smsFallBackFromPayload = get(
|
|
1610
|
+
template.value,
|
|
1611
|
+
'versions.base.content.RCS.smsFallBackContent',
|
|
1612
|
+
);
|
|
1613
|
+
const rcsCardVarMappedFromPayload = get(
|
|
1614
|
+
template.value,
|
|
1615
|
+
'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped',
|
|
1616
|
+
);
|
|
1617
|
+
next.templateData = {
|
|
1618
|
+
...baseTd,
|
|
1619
|
+
type: constants.RCS,
|
|
1620
|
+
name: template?.value?.name,
|
|
1621
|
+
versions: template?.value?.versions,
|
|
1622
|
+
...(smsFallBackFromPayload != null
|
|
1623
|
+
&& typeof smsFallBackFromPayload === 'object'
|
|
1624
|
+
&& Object.keys(smsFallBackFromPayload).length > 0
|
|
1625
|
+
? { smsFallBackContent: smsFallBackFromPayload }
|
|
1626
|
+
: {}),
|
|
1627
|
+
...(rcsCardVarMappedFromPayload != null
|
|
1628
|
+
&& typeof rcsCardVarMappedFromPayload === 'object'
|
|
1629
|
+
? { rcsCardVarMapped: rcsCardVarMappedFromPayload }
|
|
1630
|
+
: {}),
|
|
1631
|
+
};
|
|
1632
|
+
if (template._id) {
|
|
1633
|
+
next.templateData._id = template._id;
|
|
1634
|
+
}
|
|
1635
|
+
} else if (channel === constants.SMS) {
|
|
1636
|
+
const submittedSmsTemplateValue = template?.value;
|
|
1637
|
+
const smsVersions =
|
|
1638
|
+
submittedSmsTemplateValue?.history != null
|
|
1639
|
+
? submittedSmsTemplateValue
|
|
1640
|
+
: {
|
|
1641
|
+
base: submittedSmsTemplateValue?.base,
|
|
1642
|
+
history: submittedSmsTemplateValue?.base
|
|
1643
|
+
? [submittedSmsTemplateValue.base]
|
|
1644
|
+
: [],
|
|
1645
|
+
};
|
|
1646
|
+
next.templateData = {
|
|
1647
|
+
...baseTd,
|
|
1648
|
+
type: constants.SMS,
|
|
1649
|
+
name: baseTd?.name || 'Campaign message SMS content',
|
|
1650
|
+
versions: smsVersions,
|
|
1651
|
+
};
|
|
1652
|
+
if (template?._id) {
|
|
1653
|
+
next.templateData._id = template._id;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
return next;
|
|
1658
|
+
},
|
|
1659
|
+
() => {
|
|
1660
|
+
if (!template.validity) {
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
const templateData = this.state.templateData ? this.state.templateData : template; //select existing or create new content
|
|
1664
|
+
const channelForConsumer = String(
|
|
1665
|
+
templateData.type
|
|
1666
|
+
|| template.type
|
|
1667
|
+
|| get(template, 'value.type')
|
|
1668
|
+
|| '',
|
|
1669
|
+
).toUpperCase();
|
|
1670
|
+
const creativesData = this.getCreativesData(
|
|
1671
|
+
channelForConsumer,
|
|
1672
|
+
template,
|
|
1673
|
+
this.state.templateData || template,
|
|
1674
|
+
);// convers data to consumer understandable format
|
|
1675
|
+
creativesData.then((data) => {
|
|
1676
|
+
this.logGTMEvent(channelForConsumer, data);
|
|
1677
|
+
this.processCentralCommsMetaId(channelForConsumer, data, {
|
|
1678
|
+
closeSlideBoxAfterSubmit: template.closeSlideBoxAfterSubmit,
|
|
1516
1679
|
});
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1680
|
+
});
|
|
1681
|
+
},
|
|
1682
|
+
);
|
|
1520
1683
|
}
|
|
1521
1684
|
|
|
1522
|
-
processCentralCommsMetaId = (channel, creativesData) => {
|
|
1685
|
+
processCentralCommsMetaId = (channel, creativesData, options = {}) => {
|
|
1686
|
+
const { closeSlideBoxAfterSubmit = false } = options;
|
|
1687
|
+
const maybeCloseLibrarySlideBox = () => {
|
|
1688
|
+
if (
|
|
1689
|
+
closeSlideBoxAfterSubmit
|
|
1690
|
+
&& this.props.isFullMode === false
|
|
1691
|
+
&& typeof this.handleCloseSlideBox === 'function'
|
|
1692
|
+
) {
|
|
1693
|
+
this.handleCloseSlideBox();
|
|
1694
|
+
}
|
|
1695
|
+
};
|
|
1523
1696
|
// Create the payload for the centralcommnsmetaId API call
|
|
1524
1697
|
const { isLoyaltyModule = false, loyaltyMetaData = {} } = this.props;
|
|
1525
1698
|
const { actionName, setMetaData = () => { } } = loyaltyMetaData;
|
|
@@ -1545,6 +1718,7 @@ export class Creatives extends React.Component {
|
|
|
1545
1718
|
if (result?.status?.code === 200) {
|
|
1546
1719
|
setMetaData(result);
|
|
1547
1720
|
this.props.getCreativesData(creativesData);
|
|
1721
|
+
maybeCloseLibrarySlideBox();
|
|
1548
1722
|
} else {
|
|
1549
1723
|
CapNotification.error({ message: <FormattedMessage {...messages.somethingWentWrong} /> });
|
|
1550
1724
|
}
|
|
@@ -1555,6 +1729,7 @@ export class Creatives extends React.Component {
|
|
|
1555
1729
|
} else {
|
|
1556
1730
|
// If not a loyalty module or different action, should work as usual
|
|
1557
1731
|
this.props.getCreativesData(creativesData);
|
|
1732
|
+
maybeCloseLibrarySlideBox();
|
|
1558
1733
|
}
|
|
1559
1734
|
};
|
|
1560
1735
|
|
|
@@ -1587,7 +1762,9 @@ export class Creatives extends React.Component {
|
|
|
1587
1762
|
}
|
|
1588
1763
|
this.setState((prevState) => ({
|
|
1589
1764
|
...prevState,
|
|
1590
|
-
|
|
1765
|
+
// Library mode (isFullMode === false): retain last template so reopening still has RCS payload.
|
|
1766
|
+
// Undefined isFullMode defaults to full-mode close behavior (clear templateData).
|
|
1767
|
+
...(this.props.isFullMode !== false ? { templateData: undefined } : {}),
|
|
1591
1768
|
showSlideBox: false,
|
|
1592
1769
|
liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
|
|
1593
1770
|
isLiquidValidationError: false,
|
|
@@ -1798,21 +1975,12 @@ export class Creatives extends React.Component {
|
|
|
1798
1975
|
}
|
|
1799
1976
|
|
|
1800
1977
|
showLiquidErrorInFooter = (errorMessagesFromFormBuilder, currentFormBuilderTab) => {
|
|
1801
|
-
const
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
const hasStandard = !isDeepEmpty(standardMsgs);
|
|
1805
|
-
const isLiquidValidationError = hasLiquid || hasStandard;
|
|
1806
|
-
// Don't overwrite existing liquid error with empty only for Mobile Push OLD (FormBuilder/clear calls empty there); SMS/others clear on input change
|
|
1807
|
-
const isMobilePush = this.state.currentChannel?.toUpperCase() === constants.MOBILE_PUSH;
|
|
1808
|
-
if (!hasLiquid && !hasStandard && this.state.isLiquidValidationError && isMobilePush) {
|
|
1809
|
-
return;
|
|
1810
|
-
}
|
|
1811
|
-
this.setState({
|
|
1812
|
-
isLiquidValidationError,
|
|
1813
|
-
liquidErrorMessage: errorMessagesFromFormBuilder,
|
|
1814
|
-
activeFormBuilderTab: currentFormBuilderTab === 1 ? constants.ANDROID : (currentFormBuilderTab === 2 ? constants.IOS : null), // Update activeFormBuilderTab, default to 1 if undefined
|
|
1978
|
+
const next = computeLiquidFooterUpdateFromFormBuilder(errorMessagesFromFormBuilder, currentFormBuilderTab, {
|
|
1979
|
+
previousIsLiquidValidationError: this.state.isLiquidValidationError,
|
|
1980
|
+
currentChannelUpper: this.state.currentChannel?.toUpperCase(),
|
|
1815
1981
|
});
|
|
1982
|
+
if (next == null) return;
|
|
1983
|
+
this.setState(next);
|
|
1816
1984
|
}
|
|
1817
1985
|
|
|
1818
1986
|
// Callback to update HTML Editor validation state (called from EmailWrapper)
|
|
@@ -1935,6 +2103,11 @@ export class Creatives extends React.Component {
|
|
|
1935
2103
|
inAppEditorType,
|
|
1936
2104
|
htmlEditorValidationState,
|
|
1937
2105
|
} = this.state;
|
|
2106
|
+
const useLocalTemplates = get(
|
|
2107
|
+
this.props,
|
|
2108
|
+
'localTemplatesConfig.useLocalTemplates',
|
|
2109
|
+
get(this.props, 'useLocalTemplates', false),
|
|
2110
|
+
);
|
|
1938
2111
|
const {
|
|
1939
2112
|
isFullMode,
|
|
1940
2113
|
creativesMode,
|
|
@@ -1986,14 +2159,7 @@ export class Creatives extends React.Component {
|
|
|
1986
2159
|
// IMPORTANT: Never show ErrorInfoNote in footer when in HTML Editor mode, even if liquidErrorMessage exists
|
|
1987
2160
|
const shouldShowErrorInfoNoteInFooter = isHTMLEditorMode ? false : hasBEEEditorErrors;
|
|
1988
2161
|
|
|
1989
|
-
|
|
1990
|
-
const slideBoxWrapperMargin = (get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0 && get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0)
|
|
1991
|
-
? CAP_SPACE_64
|
|
1992
|
-
: get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0
|
|
1993
|
-
? CAP_SPACE_56
|
|
1994
|
-
: get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0
|
|
1995
|
-
? CAP_SPACE_32
|
|
1996
|
-
: 0;
|
|
2162
|
+
const slideBoxWrapperMargin = getSlideBoxWrapperMarginFromLiquidErrors(liquidErrorMessage);
|
|
1997
2163
|
/* TODO: Instead of passing down same props separately to each component down, write common function to these props and pass it accordingly */
|
|
1998
2164
|
|
|
1999
2165
|
// Compute anonymous user type and channel restrictions
|
|
@@ -2022,7 +2188,10 @@ export class Creatives extends React.Component {
|
|
|
2022
2188
|
<SlideBoxWrapper
|
|
2023
2189
|
slideBoxWrapperMargin={slideBoxWrapperMargin}
|
|
2024
2190
|
shouldApplyFooterMargin={shouldShowErrorInfoNoteInFooter}
|
|
2025
|
-
className={classnames(
|
|
2191
|
+
className={classnames(
|
|
2192
|
+
`${classPrefix} ${isFullMode ? 'creatives-full-mode' : 'creatives-library-mode'} ${mapTemplateCreate ? 'map-template-create' : ''}`,
|
|
2193
|
+
useLocalTemplates && slidBoxContent === 'templates' && 'creatives-slidebox--local-sms-templates',
|
|
2194
|
+
)}
|
|
2026
2195
|
>
|
|
2027
2196
|
<CapSlideBox
|
|
2028
2197
|
header={
|
|
@@ -2047,12 +2216,13 @@ export class Creatives extends React.Component {
|
|
|
2047
2216
|
smsRegister={smsRegister}
|
|
2048
2217
|
handleClose={this.handleCloseSlideBox}
|
|
2049
2218
|
moduleType={this.props.messageDetails?.type}
|
|
2219
|
+
useLocalTemplates={useLocalTemplates}
|
|
2050
2220
|
/>
|
|
2051
2221
|
)}
|
|
2052
2222
|
content={(
|
|
2053
2223
|
<SlideBoxContent
|
|
2054
2224
|
key="creatives-container-slidebox-content"
|
|
2055
|
-
onSelectTemplate={this.onSelectTemplate}
|
|
2225
|
+
onSelectTemplate={this.props.onSelectTemplate != null ? this.props.onSelectTemplate : this.onSelectTemplate}
|
|
2056
2226
|
onCreateComplete={getCreativesData}
|
|
2057
2227
|
onPreviewTemplate={this.onPreviewTemplate}
|
|
2058
2228
|
slidBoxContent={slidBoxContent}
|
|
@@ -2128,7 +2298,8 @@ export class Creatives extends React.Component {
|
|
|
2128
2298
|
isTestAndPreviewMode={(() => this.state.isTestAndPreviewMode)()}
|
|
2129
2299
|
onHtmlEditorValidationStateChange={this.updateHtmlEditorValidationState}
|
|
2130
2300
|
onPersonalizationTokensChange={this.handlePersonalizationTokensChange}
|
|
2131
|
-
|
|
2301
|
+
localTemplatesConfig={pick(this.props.localTemplatesConfig || this.props, constants.LOCAL_TEMPLATE_CONFIG_KEYS)}
|
|
2302
|
+
/>
|
|
2132
2303
|
)}
|
|
2133
2304
|
footer={this.shouldShowFooter() ? (
|
|
2134
2305
|
<SlideBoxFooter
|
|
@@ -2218,6 +2389,25 @@ Creatives.propTypes = {
|
|
|
2218
2389
|
formatMessage: PropTypes.func,
|
|
2219
2390
|
}),
|
|
2220
2391
|
stopValidation: PropTypes.func,
|
|
2392
|
+
// Local template list (e.g. for SMS fallback): when set, TemplatesV2 uses these instead of Redux.
|
|
2393
|
+
// All optional. Pass either localTemplatesConfig (object) or individual props below.
|
|
2394
|
+
localTemplatesConfig: PropTypes.shape({
|
|
2395
|
+
useLocalTemplates: PropTypes.bool,
|
|
2396
|
+
localTemplates: PropTypes.arrayOf(PropTypes.object),
|
|
2397
|
+
localTemplatesLoading: PropTypes.bool,
|
|
2398
|
+
localTemplatesFilterContent: PropTypes.node,
|
|
2399
|
+
localTemplatesSentinelContent: PropTypes.node,
|
|
2400
|
+
localTemplatesScrollContainerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
|
|
2401
|
+
localTemplatesUseSkeleton: PropTypes.bool,
|
|
2402
|
+
}),
|
|
2403
|
+
useLocalTemplates: PropTypes.bool,
|
|
2404
|
+
localTemplates: PropTypes.arrayOf(PropTypes.object),
|
|
2405
|
+
localTemplatesLoading: PropTypes.bool,
|
|
2406
|
+
localTemplatesFilterContent: PropTypes.node,
|
|
2407
|
+
localTemplatesSentinelContent: PropTypes.node,
|
|
2408
|
+
localTemplatesScrollContainerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
|
|
2409
|
+
localTemplatesUseSkeleton: PropTypes.bool,
|
|
2410
|
+
onSelectTemplate: PropTypes.func,
|
|
2221
2411
|
};
|
|
2222
2412
|
const mapStatesToProps = () => createStructuredSelector({
|
|
2223
2413
|
isLoading: isLoadingSelector(),
|