@capillarytech/creatives-library 8.0.168-beta.1 → 8.0.169
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/package.json +1 -1
- package/utils/test-utils.js +6 -2
- package/v2Components/CapActionButton/index.js +52 -12
- package/v2Components/CapActionButton/messages.js +4 -0
- package/v2Components/CapActionButton/tests/index.test.js +135 -0
- package/v2Components/CapDeviceContent/index.js +5 -0
- package/v2Components/CapInAppCTA/index.js +29 -14
- package/v2Components/CapInAppCTA/index.scss +0 -2
- package/v2Components/CapInAppCTA/messages.js +4 -0
- package/v2Components/CapMpushCTA/index.js +54 -38
- package/v2Components/CapMpushCTA/index.scss +2 -2
- package/v2Components/CapMpushCTA/messages.js +4 -0
- package/v2Components/CapTagListWithInput/index.js +169 -0
- package/v2Components/CapTagListWithInput/messages.js +10 -0
- package/v2Components/FormBuilder/index.js +93 -1
- package/v2Components/TestAndPreviewSlidebox/PreviewSection.js +1 -1
- package/v2Components/TestAndPreviewSlidebox/index.js +24 -4
- package/v2Containers/CreativesContainer/index.js +1 -1
- package/v2Containers/Email/index.js +64 -3
- package/v2Containers/Email/initialSchema.js +7 -21
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +1 -1
- package/v2Containers/MobilePush/Create/index.js +24 -3
- package/v2Containers/MobilePush/commonMethods.js +25 -3
- package/v2Containers/MobilePushNew/components/CtaButtons.js +20 -0
- package/v2Containers/MobilePushNew/components/MediaUploaders.js +31 -3
- package/v2Containers/MobilePushNew/components/PlatformContentFields.js +34 -3
- package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +200 -5
- package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +59 -1
- package/v2Containers/MobilePushNew/index.js +9 -0
- package/v2Containers/MobilePushNew/index.scss +2 -1
- package/v2Containers/Rcs/index.js +77 -71
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +15270 -492
- package/v2Containers/Viber/index.js +102 -76
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { injectIntl, intlShape } from 'react-intl';
|
|
4
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
5
|
+
import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
|
|
6
|
+
import CapInput from '@capillarytech/cap-ui-library/CapInput';
|
|
7
|
+
import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
|
|
8
|
+
import TagList from '../../v2Containers/TagList';
|
|
9
|
+
import messages from './messages';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* CapTagListWithInput - A reusable component that combines TagList and CapInput
|
|
13
|
+
* for external links/URLs that can be used across all channels
|
|
14
|
+
*/
|
|
15
|
+
export const CapTagListWithInput = (props) => {
|
|
16
|
+
const {
|
|
17
|
+
intl,
|
|
18
|
+
// TagList props
|
|
19
|
+
tags = [],
|
|
20
|
+
injectedTags = {},
|
|
21
|
+
location = {},
|
|
22
|
+
selectedOfferDetails = [],
|
|
23
|
+
onTagSelect,
|
|
24
|
+
onContextChange,
|
|
25
|
+
moduleFilterEnabled = true,
|
|
26
|
+
className = '',
|
|
27
|
+
userLocale = 'en',
|
|
28
|
+
eventContextTags = [],
|
|
29
|
+
// CapInput props
|
|
30
|
+
inputId,
|
|
31
|
+
inputValue = '',
|
|
32
|
+
inputOnChange,
|
|
33
|
+
inputPlaceholder,
|
|
34
|
+
inputErrorMessage = '',
|
|
35
|
+
inputSize = 'default',
|
|
36
|
+
inputMaxLength,
|
|
37
|
+
inputRequired = false,
|
|
38
|
+
inputDisabled = false,
|
|
39
|
+
// Layout props
|
|
40
|
+
headingText,
|
|
41
|
+
headingType = 'h4',
|
|
42
|
+
headingStyle = {},
|
|
43
|
+
containerStyle = {},
|
|
44
|
+
tagListStyle = {},
|
|
45
|
+
inputStyle = {},
|
|
46
|
+
// Custom props
|
|
47
|
+
showHeading = true,
|
|
48
|
+
showTagList = true,
|
|
49
|
+
showInput = true,
|
|
50
|
+
inputProps = {},
|
|
51
|
+
} = props;
|
|
52
|
+
|
|
53
|
+
const { formatMessage } = intl;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<CapColumn style={containerStyle}>
|
|
57
|
+
<CapRow style={{display: 'flex', flexDirection: 'row'}}>
|
|
58
|
+
{showHeading && headingText && (
|
|
59
|
+
<CapHeading type={headingType} style={headingStyle}>
|
|
60
|
+
{headingText}
|
|
61
|
+
</CapHeading>
|
|
62
|
+
)}
|
|
63
|
+
|
|
64
|
+
{showTagList && (
|
|
65
|
+
<TagList
|
|
66
|
+
key={`${inputId}_tags`}
|
|
67
|
+
className={className}
|
|
68
|
+
moduleFilterEnabled={moduleFilterEnabled}
|
|
69
|
+
label={formatMessage(messages.addLabels)}
|
|
70
|
+
onTagSelect={onTagSelect}
|
|
71
|
+
onContextChange={onContextChange}
|
|
72
|
+
location={location}
|
|
73
|
+
tags={tags}
|
|
74
|
+
injectedTags={injectedTags}
|
|
75
|
+
userLocale={userLocale}
|
|
76
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
77
|
+
eventContextTags={eventContextTags}
|
|
78
|
+
style={tagListStyle}
|
|
79
|
+
/>
|
|
80
|
+
)}
|
|
81
|
+
</CapRow>
|
|
82
|
+
{showInput && (
|
|
83
|
+
<CapInput
|
|
84
|
+
id={inputId}
|
|
85
|
+
onChange={inputOnChange}
|
|
86
|
+
placeholder={inputPlaceholder}
|
|
87
|
+
value={inputValue}
|
|
88
|
+
size={inputSize}
|
|
89
|
+
maxLength={inputMaxLength}
|
|
90
|
+
errorMessage={inputErrorMessage}
|
|
91
|
+
isRequired={inputRequired}
|
|
92
|
+
disabled={inputDisabled}
|
|
93
|
+
style={inputStyle}
|
|
94
|
+
{...inputProps}
|
|
95
|
+
/>
|
|
96
|
+
)}
|
|
97
|
+
</CapColumn>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
CapTagListWithInput.propTypes = {
|
|
102
|
+
intl: intlShape.isRequired,
|
|
103
|
+
// TagList props
|
|
104
|
+
tags: PropTypes.array,
|
|
105
|
+
injectedTags: PropTypes.object,
|
|
106
|
+
location: PropTypes.object,
|
|
107
|
+
selectedOfferDetails: PropTypes.array,
|
|
108
|
+
onTagSelect: PropTypes.func.isRequired,
|
|
109
|
+
onContextChange: PropTypes.func.isRequired,
|
|
110
|
+
moduleFilterEnabled: PropTypes.bool,
|
|
111
|
+
className: PropTypes.string,
|
|
112
|
+
userLocale: PropTypes.string,
|
|
113
|
+
eventContextTags: PropTypes.array,
|
|
114
|
+
|
|
115
|
+
// CapInput props
|
|
116
|
+
inputId: PropTypes.string.isRequired,
|
|
117
|
+
inputValue: PropTypes.string,
|
|
118
|
+
inputOnChange: PropTypes.func.isRequired,
|
|
119
|
+
inputPlaceholder: PropTypes.string,
|
|
120
|
+
inputErrorMessage: PropTypes.string,
|
|
121
|
+
inputSize: PropTypes.oneOf(['small', 'default', 'large']),
|
|
122
|
+
inputMaxLength: PropTypes.number,
|
|
123
|
+
inputRequired: PropTypes.bool,
|
|
124
|
+
inputDisabled: PropTypes.bool,
|
|
125
|
+
inputProps: PropTypes.object,
|
|
126
|
+
|
|
127
|
+
// Layout props
|
|
128
|
+
headingText: PropTypes.string,
|
|
129
|
+
headingType: PropTypes.oneOf(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']),
|
|
130
|
+
headingStyle: PropTypes.object,
|
|
131
|
+
containerStyle: PropTypes.object,
|
|
132
|
+
tagListStyle: PropTypes.object,
|
|
133
|
+
inputStyle: PropTypes.object,
|
|
134
|
+
|
|
135
|
+
// Custom props
|
|
136
|
+
showHeading: PropTypes.bool,
|
|
137
|
+
showTagList: PropTypes.bool,
|
|
138
|
+
showInput: PropTypes.bool,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
CapTagListWithInput.defaultProps = {
|
|
142
|
+
tags: [],
|
|
143
|
+
injectedTags: {},
|
|
144
|
+
location: {},
|
|
145
|
+
selectedOfferDetails: [],
|
|
146
|
+
moduleFilterEnabled: true,
|
|
147
|
+
className: '',
|
|
148
|
+
userLocale: 'en',
|
|
149
|
+
eventContextTags: [],
|
|
150
|
+
inputValue: '',
|
|
151
|
+
inputSize: 'default',
|
|
152
|
+
inputRequired: false,
|
|
153
|
+
inputDisabled: false,
|
|
154
|
+
inputPlaceholder: '',
|
|
155
|
+
inputErrorMessage: '',
|
|
156
|
+
inputMaxLength: undefined,
|
|
157
|
+
headingText: '',
|
|
158
|
+
headingType: 'h4',
|
|
159
|
+
headingStyle: {},
|
|
160
|
+
containerStyle: {},
|
|
161
|
+
tagListStyle: {},
|
|
162
|
+
inputStyle: {},
|
|
163
|
+
showHeading: true,
|
|
164
|
+
showTagList: true,
|
|
165
|
+
showInput: true,
|
|
166
|
+
inputProps: {},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export default injectIntl(CapTagListWithInput);
|
|
@@ -37,6 +37,7 @@ import { createStructuredSelector } from 'reselect';
|
|
|
37
37
|
import { CAP_SPACE_12, CAP_SPACE_08, FONT_COLOR_05, FONT_COLOR_04 } from '@capillarytech/cap-ui-library/styled/variables';
|
|
38
38
|
import TemplatePreview from '../TemplatePreview';
|
|
39
39
|
import TagList from '../../v2Containers/TagList';
|
|
40
|
+
import CapTagListWithInput from '../CapTagListWithInput';
|
|
40
41
|
import SlideBox from '../SlideBox';
|
|
41
42
|
import CardGrid from '../CardGrid';
|
|
42
43
|
import CKEditor from "../Ckeditor/";
|
|
@@ -2743,6 +2744,40 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2743
2744
|
</CapColumn>
|
|
2744
2745
|
);
|
|
2745
2746
|
break;
|
|
2747
|
+
case "cap-tag-list-with-input":
|
|
2748
|
+
ifError = this.state.checkValidation && this.state.errorData[val.id];
|
|
2749
|
+
columns.push(
|
|
2750
|
+
<CapColumn key={`input-${val.id}`} span={val.width || 10} offset={val.offset}>
|
|
2751
|
+
<CapTagListWithInput
|
|
2752
|
+
key={`input-${val.id}`}
|
|
2753
|
+
inputId={val.id}
|
|
2754
|
+
inputValue={this.state.formData[val.id] || ''}
|
|
2755
|
+
inputOnChange={(e) => this.updateFormData(e.target.value, val)}
|
|
2756
|
+
inputPlaceholder={val.placeholder || ''}
|
|
2757
|
+
inputErrorMessage={val.errorMessage && ifError ? val.errorMessage : ''}
|
|
2758
|
+
inputRequired={val.required || false}
|
|
2759
|
+
inputDisabled={val.disabled || false}
|
|
2760
|
+
headingText={val.label || ''}
|
|
2761
|
+
headingStyle={val.headingStyle || { marginTop: '3%', marginRight: '79%' }}
|
|
2762
|
+
headingType="h4"
|
|
2763
|
+
onTagSelect={(data) => this.callChildEvent(data, val, 'onTagSelect')}
|
|
2764
|
+
onContextChange={this.props.onContextChange}
|
|
2765
|
+
location={this.props.location}
|
|
2766
|
+
tags={this.props.tags ? this.props.tags : []}
|
|
2767
|
+
injectedTags={this.props.injectedTags ? this.props.injectedTags : {}}
|
|
2768
|
+
className={val.className ? val.className : ''}
|
|
2769
|
+
userLocale={this.props.userLocale}
|
|
2770
|
+
selectedOfferDetails={this.props.selectedOfferDetails}
|
|
2771
|
+
eventContextTags={this.props?.eventContextTags}
|
|
2772
|
+
moduleFilterEnabled={this.props.location && this.props.location.query && this.props.location.query.type !== 'embedded'}
|
|
2773
|
+
containerStyle={val.style || {}}
|
|
2774
|
+
inputProps={val.inputProps || {}}
|
|
2775
|
+
showInput={val.showInput !== false}
|
|
2776
|
+
showTagList={val.showTagList !== false}
|
|
2777
|
+
/>
|
|
2778
|
+
</CapColumn>
|
|
2779
|
+
);
|
|
2780
|
+
break;
|
|
2746
2781
|
case "tabs":
|
|
2747
2782
|
columns.push(
|
|
2748
2783
|
<CapColumn key="input" span={10}>
|
|
@@ -3343,7 +3378,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3343
3378
|
const currentLang = (!_.isEmpty(this.state.formData) && !_.isEmpty(this.state.formData[`${this.state.currentTab - 1}`]) && !_.isEmpty(this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages) && this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages[langIndex]) ? this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages[langIndex] : this.props.baseLanguage;
|
|
3344
3379
|
const isBEEAppEnable = this.checkBeeEditorAllowedForLibrary();
|
|
3345
3380
|
|
|
3346
|
-
|
|
3381
|
+
// Always render Subject TagList (title-tagList) even when Bee editor is active for EMAIL channel
|
|
3382
|
+
if (
|
|
3383
|
+
val.id === 'title-tagList' ||
|
|
3384
|
+
!(_.get(this.state, `formData[${this.state.currentTab - 1}][${currentLang}].is_drag_drop`, false)) ||
|
|
3385
|
+
isBEEAppEnable === false ||
|
|
3386
|
+
channel !== 'EMAIL'
|
|
3387
|
+
) {
|
|
3347
3388
|
columns.push(
|
|
3348
3389
|
<CapColumn key={`input-${val.id}`} offset={val.offset} span={val.width ? val.width : ''} style={val.style ? val.style : {marginBottom: '16px'}}>
|
|
3349
3390
|
<TagList
|
|
@@ -3366,6 +3407,57 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3366
3407
|
);
|
|
3367
3408
|
}
|
|
3368
3409
|
break;
|
|
3410
|
+
case "cap-tag-list-with-input":
|
|
3411
|
+
let moduleFilterEnabledForCapTagList = this.props.location && this.props.location.query && this.props.location.query.type !== 'embedded';
|
|
3412
|
+
const channelForCapTagList = _.get(this.props, 'schema.channel', "");
|
|
3413
|
+
if (channelForCapTagList === 'EMAIL') {
|
|
3414
|
+
moduleFilterEnabledForCapTagList = this.props.isFullMode;
|
|
3415
|
+
}
|
|
3416
|
+
const langIndexForCapTagList = 0;
|
|
3417
|
+
const currentLangForCapTagList = (!_.isEmpty(this.state.formData) && !_.isEmpty(this.state.formData[`${this.state.currentTab - 1}`]) && !_.isEmpty(this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages) && this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages[langIndexForCapTagList]) ? this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages[langIndexForCapTagList] : this.props.baseLanguage;
|
|
3418
|
+
const isBEEAppEnableForCapTagList = this.checkBeeEditorAllowedForLibrary();
|
|
3419
|
+
ifError = this.state.checkValidation && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
|
|
3420
|
+
|
|
3421
|
+
// Always render Subject CapTagListWithInput even when Bee editor is active for EMAIL channel
|
|
3422
|
+
if (
|
|
3423
|
+
val.id === 'template-subject' ||
|
|
3424
|
+
!(_.get(this.state, `formData[${this.state.currentTab - 1}][${currentLangForCapTagList}].is_drag_drop`, false)) ||
|
|
3425
|
+
isBEEAppEnableForCapTagList === false ||
|
|
3426
|
+
channelForCapTagList !== 'EMAIL'
|
|
3427
|
+
) {
|
|
3428
|
+
columns.push(
|
|
3429
|
+
<CapColumn key={`input-${val.id}`} offset={val.offset} span={val.width ? val.width : ''} style={val.style ? val.style : {marginBottom: '16px'}}>
|
|
3430
|
+
<CapTagListWithInput
|
|
3431
|
+
key={`input-${val.id}`}
|
|
3432
|
+
inputId={val.id}
|
|
3433
|
+
inputValue={this.state.formData[val.id] || ''}
|
|
3434
|
+
inputOnChange={(e) => this.updateFormData(e.target.value, val)}
|
|
3435
|
+
inputPlaceholder={val.placeholder || ''}
|
|
3436
|
+
inputErrorMessage={val.errorMessage && ifError ? val.errorMessage : ''}
|
|
3437
|
+
inputRequired={val.required || false}
|
|
3438
|
+
inputDisabled={val.disabled || false}
|
|
3439
|
+
headingText={val.label || ''}
|
|
3440
|
+
headingStyle={val.headingStyle || { marginTop: '3%', marginRight: '79%' }}
|
|
3441
|
+
headingType="h4"
|
|
3442
|
+
onTagSelect={(data) => this.callChildEvent(data, val, 'onTagSelect')}
|
|
3443
|
+
onContextChange={this.props.onContextChange}
|
|
3444
|
+
location={this.props.location}
|
|
3445
|
+
tags={this.props.tags ? this.props.tags : []}
|
|
3446
|
+
injectedTags={this.props.injectedTags ? this.props.injectedTags : {}}
|
|
3447
|
+
className={val.className ? val.className : ''}
|
|
3448
|
+
userLocale={this.state.translationLang}
|
|
3449
|
+
selectedOfferDetails={this.props.selectedOfferDetails}
|
|
3450
|
+
eventContextTags={this.props?.eventContextTags}
|
|
3451
|
+
moduleFilterEnabled={moduleFilterEnabledForCapTagList}
|
|
3452
|
+
containerStyle={val.style || {}}
|
|
3453
|
+
inputProps={val.inputProps || {}}
|
|
3454
|
+
showInput={val.showInput !== false}
|
|
3455
|
+
showTagList={val.showTagList !== false}
|
|
3456
|
+
/>
|
|
3457
|
+
</CapColumn>
|
|
3458
|
+
);
|
|
3459
|
+
}
|
|
3460
|
+
break;
|
|
3369
3461
|
case "button":
|
|
3370
3462
|
if (styling === 'semantic' && val.metaType === 'submit-button') {
|
|
3371
3463
|
columns.push(
|
|
@@ -20,7 +20,7 @@ const PreviewSection = ({
|
|
|
20
20
|
device={previewDevice}
|
|
21
21
|
onDeviceChange={setPreviewDevice}
|
|
22
22
|
customer={selectedCustomer}
|
|
23
|
-
subject={formData['template-subject']}
|
|
23
|
+
subject={previewDataHtml?.resolvedTitle || formData['template-subject']}
|
|
24
24
|
>
|
|
25
25
|
{isUpdatingPreview && (
|
|
26
26
|
<CapRow className="loading-container">
|
|
@@ -108,6 +108,21 @@ const TestAndPreviewSlidebox = (props) => {
|
|
|
108
108
|
requiredTags.some((tag) => !customValues[tag.fullPath])
|
|
109
109
|
), [requiredTags, customValues]);
|
|
110
110
|
|
|
111
|
+
// Function to resolve tags in text with custom values
|
|
112
|
+
const resolveTagsInText = (text, tagValues) => {
|
|
113
|
+
if (!text) return text;
|
|
114
|
+
let resolvedText = text;
|
|
115
|
+
|
|
116
|
+
// Replace each tag with its custom value
|
|
117
|
+
Object.keys(tagValues).forEach((tagPath) => {
|
|
118
|
+
const tagName = tagPath.split('.').pop(); // Get the actual tag name from the path
|
|
119
|
+
const tagRegex = new RegExp(`{{${tagName}}}`, 'g');
|
|
120
|
+
resolvedText = resolvedText.replace(tagRegex, tagValues[tagPath] || `{{${tagName}}}`);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return resolvedText;
|
|
124
|
+
};
|
|
125
|
+
|
|
111
126
|
// Get the current content based on editor type
|
|
112
127
|
const getCurrentContent = useMemo(() => {
|
|
113
128
|
const currentTabData = formData[currentTab - 1];
|
|
@@ -380,9 +395,10 @@ const TestAndPreviewSlidebox = (props) => {
|
|
|
380
395
|
setCustomValues(updatedValues);
|
|
381
396
|
|
|
382
397
|
// Update preview with prefilled values
|
|
398
|
+
const resolvedSubject = resolveTagsInText(formData['template-subject'], updatedValues);
|
|
383
399
|
const payload = {
|
|
384
400
|
channel: EMAIL,
|
|
385
|
-
messageTitle:
|
|
401
|
+
messageTitle: resolvedSubject,
|
|
386
402
|
messageBody: getCurrentContent,
|
|
387
403
|
resolvedTags: updatedValues,
|
|
388
404
|
userId: selectedCustomer?.customerId,
|
|
@@ -501,9 +517,10 @@ const TestAndPreviewSlidebox = (props) => {
|
|
|
501
517
|
setCustomValues(emptyValues);
|
|
502
518
|
|
|
503
519
|
// Update preview with empty values
|
|
520
|
+
const resolvedSubject = resolveTagsInText(formData['template-subject'], emptyValues);
|
|
504
521
|
const payload = {
|
|
505
522
|
channel: EMAIL,
|
|
506
|
-
messageTitle:
|
|
523
|
+
messageTitle: resolvedSubject,
|
|
507
524
|
messageBody: getCurrentContent,
|
|
508
525
|
resolvedTags: emptyValues,
|
|
509
526
|
userId: selectedCustomer?.customerId,
|
|
@@ -517,9 +534,12 @@ const TestAndPreviewSlidebox = (props) => {
|
|
|
517
534
|
// Include unsubscribe tag if content contains it
|
|
518
535
|
const resolvedTags = { ...customValues };
|
|
519
536
|
|
|
537
|
+
// Resolve subject tags with custom values
|
|
538
|
+
const resolvedSubject = resolveTagsInText(formData['template-subject'], customValues);
|
|
539
|
+
|
|
520
540
|
const payload = {
|
|
521
541
|
channel: EMAIL,
|
|
522
|
-
messageTitle:
|
|
542
|
+
messageTitle: resolvedSubject,
|
|
523
543
|
messageBody: getCurrentContent,
|
|
524
544
|
resolvedTags,
|
|
525
545
|
userId: selectedCustomer?.customerId,
|
|
@@ -585,7 +605,7 @@ const TestAndPreviewSlidebox = (props) => {
|
|
|
585
605
|
emailMessageContent: {
|
|
586
606
|
channel: EMAIL,
|
|
587
607
|
messageBody: previewData?.resolvedBody || getCurrentContent,
|
|
588
|
-
messageSubject: previewData?.resolvedTitle || formData['template-subject'],
|
|
608
|
+
messageSubject: previewData?.resolvedTitle || resolveTagsInText(formData['template-subject'], customValues),
|
|
589
609
|
},
|
|
590
610
|
}, messageMetaConfigId, (response) => {
|
|
591
611
|
const payload = {
|
|
@@ -1715,6 +1715,6 @@ const withConnect = connect(mapStatesToProps, mapDispatchToProps);
|
|
|
1715
1715
|
const withReducer = injectReducer({ key: 'creativesContainer', reducer: creativesContainerReducer });
|
|
1716
1716
|
const withSaga = injectSaga({ key: 'cap', saga: capSagaForFetchSchemaForEntity });
|
|
1717
1717
|
const withLiquidSaga = injectSaga({ key: 'liquid', saga: capSagaLiquidEntity, mode: DAEMON });
|
|
1718
|
-
const withDefaultTempSaga = injectSaga({ key: 'creativesContainer', saga: v2TemplateSagaWatchGetDefaultBeeTemplates
|
|
1718
|
+
const withDefaultTempSaga = injectSaga({ key: 'creativesContainer', saga: v2TemplateSagaWatchGetDefaultBeeTemplates });
|
|
1719
1719
|
|
|
1720
1720
|
export default compose(withSaga, withLiquidSaga, withDefaultTempSaga, withReducer, withConnect)(injectIntl(Creatives));
|
|
@@ -100,6 +100,14 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
100
100
|
"template-name": {
|
|
101
101
|
onChange: this.onTemplateNameChange,
|
|
102
102
|
},
|
|
103
|
+
// allow tag insertion into Subject input field via a dedicated TagList
|
|
104
|
+
"title-tagList": {
|
|
105
|
+
onTagSelect: this.onTagSelect,
|
|
106
|
+
},
|
|
107
|
+
// unified subject field with input and tag selection
|
|
108
|
+
"template-subject": {
|
|
109
|
+
onTagSelect: this.onTagSelect,
|
|
110
|
+
},
|
|
103
111
|
"template-version": {
|
|
104
112
|
onSelect: this.handleVersionSelect,
|
|
105
113
|
addVersion: this.addVersion,
|
|
@@ -646,7 +654,44 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
646
654
|
|
|
647
655
|
}
|
|
648
656
|
|
|
649
|
-
onTagSelect = (data) => {
|
|
657
|
+
onTagSelect = (data, currentTab, sourceVal) => {
|
|
658
|
+
// If tag is selected from Subject's TagList or unified subject field, insert into subject input
|
|
659
|
+
if (sourceVal && (sourceVal.id === 'title-tagList' || sourceVal.id === 'template-subject')) {
|
|
660
|
+
const tagToInsert = `{{${data}}}`;
|
|
661
|
+
const formData = _.cloneDeep(this.state.formData);
|
|
662
|
+
const fieldId = 'template-subject';
|
|
663
|
+
const input = document.getElementById(fieldId) || document.querySelector(`#${fieldId} input`);
|
|
664
|
+
let subjectValue = formData[fieldId] || '';
|
|
665
|
+
try {
|
|
666
|
+
if (input && (typeof input.selectionStart === 'number')) {
|
|
667
|
+
const startPos = input.selectionStart;
|
|
668
|
+
const endPos = input.selectionEnd;
|
|
669
|
+
subjectValue = `${subjectValue.substring(0, startPos)}${tagToInsert}${subjectValue.substring(endPos)}`;
|
|
670
|
+
formData[fieldId] = subjectValue;
|
|
671
|
+
this.setState({ formData }, () => {
|
|
672
|
+
try {
|
|
673
|
+
input.focus();
|
|
674
|
+
const newPos = startPos + tagToInsert.length;
|
|
675
|
+
input.selectionStart = newPos;
|
|
676
|
+
input.selectionEnd = newPos;
|
|
677
|
+
} catch (e) {}
|
|
678
|
+
});
|
|
679
|
+
} else {
|
|
680
|
+
subjectValue = `${subjectValue}${tagToInsert}`;
|
|
681
|
+
formData[fieldId] = subjectValue;
|
|
682
|
+
this.setState({ formData });
|
|
683
|
+
if (input) {
|
|
684
|
+
try { input.value = subjectValue; } catch (e) {}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
} catch (e) {
|
|
688
|
+
// fallback safe append
|
|
689
|
+
formData[fieldId] = `${subjectValue}${tagToInsert}`;
|
|
690
|
+
this.setState({ formData });
|
|
691
|
+
}
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
|
|
650
695
|
const isDragDrop = this.state.formData[`${this.state.currentTab - 1}`][this.state.formData[`${this.state.currentTab - 1}`].activeTab].is_drag_drop;
|
|
651
696
|
const formData = _.cloneDeep(this.state.formData);
|
|
652
697
|
const isBEESupport = (this.props.location.query.isBEESupport !== "false") || false;
|
|
@@ -1546,8 +1591,24 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
1546
1591
|
if (schema.standalone) {
|
|
1547
1592
|
if ((this.props.location.query.module === "loyalty" || this.props.location.query.module === "dvs" || this.props.location.query.module === "timeline" || this.props.location.query.module === "coupon_expiry" ||
|
|
1548
1593
|
this.props.location.query.module === "library")) {
|
|
1549
|
-
|
|
1550
|
-
|
|
1594
|
+
// For the new unified cap-tag-list-with-input component, we want to show the Add label button
|
|
1595
|
+
// but hide the input field in library mode
|
|
1596
|
+
const subjectField = schema.standalone.sections[0].inputFields[0];
|
|
1597
|
+
if (subjectField && subjectField.type === "cap-tag-list-with-input") {
|
|
1598
|
+
// Only hide input in library mode, keep it visible in other modes
|
|
1599
|
+
if (this.props.location.query.module === "library") {
|
|
1600
|
+
_.set(schema, 'standalone.sections[0].inputFields[0].showInput', false);
|
|
1601
|
+
_.set(schema, 'standalone.sections[0].inputFields[0].showTagList', true);
|
|
1602
|
+
} else {
|
|
1603
|
+
// In other modes (loyalty, dvs, timeline, coupon_expiry), keep both input and tag list visible
|
|
1604
|
+
_.set(schema, 'standalone.sections[0].inputFields[0].showInput', true);
|
|
1605
|
+
_.set(schema, 'standalone.sections[0].inputFields[0].showTagList', true);
|
|
1606
|
+
}
|
|
1607
|
+
} else {
|
|
1608
|
+
// For backward compatibility with old separate fields
|
|
1609
|
+
_.set(schema, 'standalone.sections[0].inputFields[0].style.display', "" );
|
|
1610
|
+
_.set(schema, 'standalone.sections[0].inputFields[0].labelStyle.display', "");
|
|
1611
|
+
}
|
|
1551
1612
|
}
|
|
1552
1613
|
this.setState({schema});
|
|
1553
1614
|
}
|
|
@@ -20,7 +20,7 @@ export const response = {
|
|
|
20
20
|
{
|
|
21
21
|
id: "template-subject",
|
|
22
22
|
placeholder: "Enter Email Subject",
|
|
23
|
-
type: "input",
|
|
23
|
+
type: "cap-tag-list-with-input",
|
|
24
24
|
metaType: "text",
|
|
25
25
|
datatype: "string",
|
|
26
26
|
required: true,
|
|
@@ -28,29 +28,15 @@ export const response = {
|
|
|
28
28
|
styling: "semantic",
|
|
29
29
|
order: 1,
|
|
30
30
|
width: 16,
|
|
31
|
-
customComponent:
|
|
31
|
+
customComponent: true,
|
|
32
32
|
standalone: true,
|
|
33
33
|
onlyDisplay: false,
|
|
34
34
|
errorMessage: "Email Subject cannot be empty.",
|
|
35
|
+
supportedEvents: [
|
|
36
|
+
"onChange",
|
|
37
|
+
"onTagSelect",
|
|
38
|
+
],
|
|
35
39
|
},
|
|
36
|
-
// {
|
|
37
|
-
// offset: 20,
|
|
38
|
-
// id: "title-tagList",
|
|
39
|
-
// label: "Tags",
|
|
40
|
-
// target: "message-title2",
|
|
41
|
-
// type: "tag-list",
|
|
42
|
-
// metaType: "List",
|
|
43
|
-
// datatype: "select",
|
|
44
|
-
// required: true,
|
|
45
|
-
// fluid: true,
|
|
46
|
-
// onlyDisplay: true,
|
|
47
|
-
// styling: "semantic",
|
|
48
|
-
// order: 1,
|
|
49
|
-
// customComponent: true,
|
|
50
|
-
// supportedEvents: [
|
|
51
|
-
// "onTagSelect",
|
|
52
|
-
// ],
|
|
53
|
-
// },
|
|
54
40
|
],
|
|
55
41
|
},
|
|
56
42
|
],
|
|
@@ -283,7 +269,7 @@ export const response = {
|
|
|
283
269
|
metaType: "label",
|
|
284
270
|
type: "div",
|
|
285
271
|
primitive: true,
|
|
286
|
-
value: <CapButton isAddBtn type="flat"><FormattedMessage {...messages.image}/></CapButton>,
|
|
272
|
+
value: <CapButton isAddBtn type="flat"><FormattedMessage {...messages.image} /></CapButton>,
|
|
287
273
|
fluid: true,
|
|
288
274
|
onlyDisplay: false,
|
|
289
275
|
styling: "semantic",
|
|
@@ -291,7 +291,29 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
291
291
|
rowClassName: 'mobile-push-row',
|
|
292
292
|
id: 'cta-external-link',
|
|
293
293
|
cols: [
|
|
294
|
-
|
|
294
|
+
{
|
|
295
|
+
// Add label control for external link input
|
|
296
|
+
id: "title-tagList",
|
|
297
|
+
label: "Add label",
|
|
298
|
+
type: "tag-list",
|
|
299
|
+
metaType: "List",
|
|
300
|
+
datatype: "select",
|
|
301
|
+
required: true,
|
|
302
|
+
fluid: true,
|
|
303
|
+
onlyDisplay: true,
|
|
304
|
+
style: {
|
|
305
|
+
marginRight: "10%",
|
|
306
|
+
marginTop: "-3%",
|
|
307
|
+
},
|
|
308
|
+
styling: "semantic",
|
|
309
|
+
order: 2,
|
|
310
|
+
customComponent: true,
|
|
311
|
+
// Helps handler identify the destination input
|
|
312
|
+
target: inputId,
|
|
313
|
+
supportedEvents: [
|
|
314
|
+
"onTagSelect",
|
|
315
|
+
],
|
|
316
|
+
},
|
|
295
317
|
{
|
|
296
318
|
id: inputId,
|
|
297
319
|
placeholder: this.props.intl.formatMessage(messages.externalLink),
|
|
@@ -304,8 +326,7 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
304
326
|
width: 18,
|
|
305
327
|
offset: 1,
|
|
306
328
|
style: {
|
|
307
|
-
width: '
|
|
308
|
-
|
|
329
|
+
width: '95%',
|
|
309
330
|
},
|
|
310
331
|
styling: "semantic",
|
|
311
332
|
order: 1,
|
|
@@ -202,11 +202,33 @@ function getLinkTypeFields({inputFieldsArgs, fieldIndex, deepLinkOptions, formDa
|
|
|
202
202
|
identifier: inputId,
|
|
203
203
|
|
|
204
204
|
cols: [
|
|
205
|
-
|
|
205
|
+
{
|
|
206
|
+
// Add label control for external link input
|
|
207
|
+
id: "title-tagList",
|
|
208
|
+
label: "Add label",
|
|
209
|
+
type: "tag-list",
|
|
210
|
+
metaType: "List",
|
|
211
|
+
datatype: "select",
|
|
212
|
+
required: true,
|
|
213
|
+
fluid: true,
|
|
214
|
+
onlyDisplay: true,
|
|
215
|
+
style: {
|
|
216
|
+
marginRight: "10%",
|
|
217
|
+
marginTop: "-3%",
|
|
218
|
+
},
|
|
219
|
+
styling: "semantic",
|
|
220
|
+
order: 2,
|
|
221
|
+
customComponent: true,
|
|
222
|
+
// Helps handler identify the destination input
|
|
223
|
+
target: inputId,
|
|
224
|
+
supportedEvents: [
|
|
225
|
+
"onTagSelect",
|
|
226
|
+
],
|
|
227
|
+
},
|
|
206
228
|
{
|
|
207
229
|
id: inputId,
|
|
208
230
|
placeholder: "External Link",
|
|
209
|
-
type: "input",
|
|
231
|
+
type: "input", //Resembles component to be used
|
|
210
232
|
metaType: "text",
|
|
211
233
|
datatype: "string",
|
|
212
234
|
errorMessage: <FormattedMessage {...messages.invalidExternalLink}/>,
|
|
@@ -215,7 +237,7 @@ function getLinkTypeFields({inputFieldsArgs, fieldIndex, deepLinkOptions, formDa
|
|
|
215
237
|
width: 18,
|
|
216
238
|
offset: 1,
|
|
217
239
|
style: {
|
|
218
|
-
width: '
|
|
240
|
+
width: '95%',
|
|
219
241
|
},
|
|
220
242
|
styling: "semantic",
|
|
221
243
|
order: 1,
|
|
@@ -19,6 +19,11 @@ const CtaButtons = ({
|
|
|
19
19
|
updateHandler,
|
|
20
20
|
deleteHandler,
|
|
21
21
|
deepLink,
|
|
22
|
+
location,
|
|
23
|
+
tags,
|
|
24
|
+
injectedTags,
|
|
25
|
+
selectedOfferDetails,
|
|
26
|
+
handleOnTagsContextChange,
|
|
22
27
|
}) => {
|
|
23
28
|
// Local state to control CTA form visibility
|
|
24
29
|
const [showPrimaryCTA, setShowPrimaryCTA] = useState(false);
|
|
@@ -130,6 +135,11 @@ const CtaButtons = ({
|
|
|
130
135
|
deleteHandler={handleDelete}
|
|
131
136
|
deepLink={deepLink}
|
|
132
137
|
buttonType={PRIMARY}
|
|
138
|
+
location={location}
|
|
139
|
+
tags={tags}
|
|
140
|
+
injectedTags={injectedTags}
|
|
141
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
142
|
+
handleOnTagsContextChange={handleOnTagsContextChange}
|
|
133
143
|
/>
|
|
134
144
|
)}
|
|
135
145
|
{isPrimaryButtonSaved && !isSecondaryButtonSaved && !shouldShowSecondaryCTA && (
|
|
@@ -174,10 +184,20 @@ CtaButtons.propTypes = {
|
|
|
174
184
|
updateHandler: PropTypes.func.isRequired,
|
|
175
185
|
deleteHandler: PropTypes.func.isRequired,
|
|
176
186
|
deepLink: PropTypes.array,
|
|
187
|
+
handleOnTagsContextChange: PropTypes.func,
|
|
188
|
+
location: PropTypes.object,
|
|
189
|
+
tags: PropTypes.array,
|
|
190
|
+
injectedTags: PropTypes.object,
|
|
191
|
+
selectedOfferDetails: PropTypes.array,
|
|
177
192
|
};
|
|
178
193
|
|
|
179
194
|
CtaButtons.defaultProps = {
|
|
180
195
|
deepLink: [],
|
|
196
|
+
handleOnTagsContextChange: () => {},
|
|
197
|
+
location: {},
|
|
198
|
+
tags: [],
|
|
199
|
+
injectedTags: {},
|
|
200
|
+
selectedOfferDetails: [],
|
|
181
201
|
};
|
|
182
202
|
|
|
183
203
|
export default memo(CtaButtons);
|