@capillarytech/creatives-library 9.0.70 → 9.0.71-alpha.sprint-cap-211543.17
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 +0 -1
- package/package.json +1 -1
- package/utils/common.js +0 -6
- package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +58 -1
- package/v2Components/CapTagListWithInput/index.js +64 -35
- package/v2Components/CapTagListWithInput/index.scss +9 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/EmailPreviewContent.js +8 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +7 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +1 -0
- package/v2Components/CommonTestAndPreview/actions.js +1 -1
- package/v2Components/CommonTestAndPreview/index.js +29 -13
- package/v2Components/CommonTestAndPreview/sagas.js +2 -1
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/EmailPreviewContent.test.js +27 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +40 -0
- package/v2Components/CommonTestAndPreview/tests/index.test.js +95 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +24 -0
- package/v2Components/EmailMobilePreview/index.js +8 -1
- package/v2Components/EmailMobilePreview/index.scss +12 -3
- package/v2Components/EmailMobilePreview/tests/index.test.js +31 -0
- package/v2Components/EmailPreviewV2/_emailPreviewV2.scss +35 -5
- package/v2Components/EmailPreviewV2/index.js +18 -6
- package/v2Components/EmailPreviewV2/messages.js +4 -0
- package/v2Components/EmailPreviewV2/tests/index.test.js +43 -0
- package/v2Components/FormBuilder/Classic.js +113 -10
- package/v2Components/FormBuilder/_formBuilder.scss +18 -0
- package/v2Components/FormBuilder/messages.js +8 -0
- package/v2Containers/Cap/tests/index.test.js +1 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +2 -1
- package/v2Containers/CommunicationFlow/Tests/constants.test.js +4 -4
- package/v2Containers/CommunicationFlow/constants.js +1 -0
- package/v2Containers/CommunicationFlow/utils/Tests/mapCommDefinitionToStepData.test.js +2 -1
- package/v2Containers/CommunicationFlow/utils/mapCommDefinitionToStepData.js +1 -0
- package/v2Containers/CreativesContainer/index.js +9 -3
- package/v2Containers/CreativesContainer/tests/index.test.js +81 -0
- package/v2Containers/Email/index.js +31 -4
- package/v2Containers/Email/initialSchema.js +30 -0
- package/v2Containers/Email/messages.js +8 -0
- package/v2Containers/Email/tests/index.test.js +107 -0
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +168 -8
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +72 -3
- package/v2Containers/EmailWrapper/components/_emailHTMLEditor.scss +26 -0
- package/v2Containers/EmailWrapper/constants.js +5 -0
- package/v2Containers/EmailWrapper/messages.js +20 -0
- package/v2Containers/TagList/index.js +7 -0
- package/v2Containers/TagList/tests/TagList.test.js +42 -0
- package/v2Containers/Templates/constants.js +1 -0
- package/v2Containers/Templates/index.js +31 -27
- package/v2Containers/Templates/messages.js +4 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +128 -4
- package/v2Containers/Templates/tests/archival.test.js +221 -0
|
@@ -31,6 +31,7 @@ import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
|
|
|
31
31
|
import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
32
32
|
import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
|
|
33
33
|
import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
|
|
34
|
+
import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
|
|
34
35
|
|
|
35
36
|
import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
|
|
36
37
|
import LabelHOC from '@capillarytech/cap-ui-library/assets/HOCs/ComponentWithLabelHOC';
|
|
@@ -61,7 +62,7 @@ import { convert } from 'html-to-text';
|
|
|
61
62
|
import { OUTBOUND, ADD_LANGUAGE, UPLOAD, USE_EDITOR, COPY_PRIMARY_LANGUAGE, GLOBAL_CONVERT_OPTIONS } from './constants';
|
|
62
63
|
import { GET_TRANSLATION_MAPPED } from '../../constants/unified';
|
|
63
64
|
import moment from 'moment';
|
|
64
|
-
import { CUSTOMER_BARCODE_TAG , COPY_OF, ENTRY_TRIGGER_TAG_REGEX, SKIP_TAGS_REGEX_GROUPS} from '../../constants/unified';
|
|
65
|
+
import { CUSTOMER_BARCODE_TAG , COPY_OF, ENTRY_TRIGGER_TAG_REGEX, SKIP_TAGS_REGEX_GROUPS, UNSUBSCRIBE_TAG} from '../../constants/unified';
|
|
65
66
|
import { REQUEST } from '../../v2Containers/Cap/constants'
|
|
66
67
|
import { isEmailUnsubscribeTagOptional, isAiContentBotDisabled } from '../../utils/common';
|
|
67
68
|
import { isUrl } from '../../v2Containers/Line/Container/Wrapper/utils';
|
|
@@ -70,6 +71,7 @@ import { getChannelData, hasPersonalizationTags, validateLiquidTemplateContent,
|
|
|
70
71
|
const {Column} = Table;
|
|
71
72
|
const {TextArea} = CapInput;
|
|
72
73
|
const {CapRadioGroup} = CapRadio;
|
|
74
|
+
const {CapLabelInline} = CapLabel;
|
|
73
75
|
// import styled from 'styled-components';
|
|
74
76
|
|
|
75
77
|
const tagsTypes = {
|
|
@@ -110,21 +112,32 @@ class HighFreqInput extends React.Component {
|
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
//
|
|
115
|
+
// EMAIL fields kept visible in BEE mode; use the blur-commit HighFreqTagInput below.
|
|
116
|
+
const HIGH_FREQ_EMAIL_TAG_INPUT_IDS = ['template-subject', 'template-preheader'];
|
|
117
|
+
|
|
118
|
+
// EMAIL subject/preheader wrapper: commits on blur, like HighFreqInput.
|
|
114
119
|
class HighFreqTagInput extends React.Component {
|
|
115
120
|
constructor(props) {
|
|
116
121
|
super(props);
|
|
117
|
-
this.state = { localInputValue: props.inputValue || '' };
|
|
122
|
+
this.state = { localInputValue: props.inputValue || '', errorDismissed: false };
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
componentDidUpdate(prevProps) {
|
|
121
126
|
if (prevProps.inputValue !== this.props.inputValue && this.state.localInputValue !== this.props.inputValue) {
|
|
122
127
|
this.setState({ localInputValue: this.props.inputValue || '' });
|
|
123
128
|
}
|
|
129
|
+
// New errorDataRef = fresh validation (each Save): un-dismiss so a repeated error shows again.
|
|
130
|
+
if (this.props.errorDataRef !== undefined && prevProps.errorDataRef !== this.props.errorDataRef && this.state.errorDismissed) {
|
|
131
|
+
this.setState({ errorDismissed: false });
|
|
132
|
+
}
|
|
124
133
|
}
|
|
125
134
|
|
|
126
135
|
handleInputChange = (e) => {
|
|
127
136
|
this.setState({ localInputValue: e.target.value });
|
|
137
|
+
// Hide a stale error on edit (like the HTML editor's Pre-header); re-validates only on Save.
|
|
138
|
+
if (this.props.errorDataRef !== undefined && !this.state.errorDismissed) {
|
|
139
|
+
this.setState({ errorDismissed: true });
|
|
140
|
+
}
|
|
128
141
|
};
|
|
129
142
|
|
|
130
143
|
handleBlur = () => {
|
|
@@ -132,13 +145,39 @@ class HighFreqTagInput extends React.Component {
|
|
|
132
145
|
};
|
|
133
146
|
|
|
134
147
|
render() {
|
|
135
|
-
const {
|
|
148
|
+
const {
|
|
149
|
+
inputValue: _iv, onCommit: _oc, inputOnChange: _ic, inputMaxLength, onRemove, removeLabel,
|
|
150
|
+
errorDataRef, inputErrorMessage, ...rest
|
|
151
|
+
} = this.props;
|
|
152
|
+
const counterSuffix = inputMaxLength ? (
|
|
153
|
+
<CapLabelInline type="label1" className="high-freq-tag-input-counter">
|
|
154
|
+
{`${this.state.localInputValue.length} / ${inputMaxLength}`}
|
|
155
|
+
</CapLabelInline>
|
|
156
|
+
) : null;
|
|
157
|
+
const removeSuffix = onRemove ? (
|
|
158
|
+
<CapLabelInline type="label1" role="button" tabIndex={0} className="high-freq-tag-input-remove-link" onClick={onRemove}>
|
|
159
|
+
{removeLabel}
|
|
160
|
+
</CapLabelInline>
|
|
161
|
+
) : null;
|
|
162
|
+
const combinedSuffix = (counterSuffix || removeSuffix) ? (
|
|
163
|
+
<CapRow align="middle" type="flex" noWrap gap={8}>
|
|
164
|
+
{counterSuffix}
|
|
165
|
+
{removeSuffix}
|
|
166
|
+
</CapRow>
|
|
167
|
+
) : undefined;
|
|
136
168
|
return (
|
|
137
169
|
<CapTagListWithInput
|
|
138
170
|
{...rest}
|
|
171
|
+
inputMaxLength={inputMaxLength}
|
|
139
172
|
inputValue={this.state.localInputValue}
|
|
140
173
|
inputOnChange={this.handleInputChange}
|
|
141
|
-
|
|
174
|
+
inputErrorMessage={this.state.errorDismissed ? '' : inputErrorMessage}
|
|
175
|
+
inputProps={{
|
|
176
|
+
...(this.props.inputProps || {}),
|
|
177
|
+
onBlur: this.handleBlur,
|
|
178
|
+
...(combinedSuffix ? { suffix: combinedSuffix } : {}),
|
|
179
|
+
}}
|
|
180
|
+
singleLineLayout
|
|
142
181
|
/>
|
|
143
182
|
);
|
|
144
183
|
}
|
|
@@ -169,6 +208,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
169
208
|
STANDARD_ERROR_MSG: [],
|
|
170
209
|
LIQUID_ERROR_MSG: [],
|
|
171
210
|
},
|
|
211
|
+
// Pre-header visibility: null = show if it has content; true/false = user's Add/Remove click.
|
|
212
|
+
preheaderRevealed: null,
|
|
172
213
|
};
|
|
173
214
|
this.renderForm = this.renderForm.bind(this);
|
|
174
215
|
this.updateFormData = this.updateFormData.bind(this);
|
|
@@ -1245,6 +1286,16 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1245
1286
|
}
|
|
1246
1287
|
}
|
|
1247
1288
|
|
|
1289
|
+
// Pre-header must not contain the unsubscribe tag (checked on Save, not per keystroke).
|
|
1290
|
+
if (index === 'template-preheader') {
|
|
1291
|
+
if (hasUnsubscribeTag(data)) {
|
|
1292
|
+
errorData[index] = true;
|
|
1293
|
+
isValid = false;
|
|
1294
|
+
} else {
|
|
1295
|
+
errorData[index] = false;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1248
1299
|
// Check if Template name present in full mode
|
|
1249
1300
|
if(type !== 'embedded' && index === 'template-name' && data === '') {
|
|
1250
1301
|
errorData[index] = true;
|
|
@@ -3447,6 +3498,18 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3447
3498
|
let contentSections = [];
|
|
3448
3499
|
let popoverSections = [];
|
|
3449
3500
|
const self = this;
|
|
3501
|
+
// Pre-header visibility: null = show if it has content; true/false = user's Add/Remove click.
|
|
3502
|
+
const preheaderValueForRenderSection = this.state.formData['template-preheader'] || '';
|
|
3503
|
+
const isPreheaderRevealed = this.state.preheaderRevealed !== null
|
|
3504
|
+
? this.state.preheaderRevealed
|
|
3505
|
+
: !!preheaderValueForRenderSection;
|
|
3506
|
+
// Hidden Pre-header section renders no fields by design — not the "not ready" spinner case below.
|
|
3507
|
+
const isHiddenPreheaderOnlySection = !isPreheaderRevealed
|
|
3508
|
+
&& section.inputFields
|
|
3509
|
+
&& section.inputFields.length === 1
|
|
3510
|
+
&& section.inputFields[0].cols
|
|
3511
|
+
&& section.inputFields[0].cols.length === 1
|
|
3512
|
+
&& section.inputFields[0].cols[0].id === 'template-preheader';
|
|
3450
3513
|
_.forEach(section.inputFields, (field) => {
|
|
3451
3514
|
const columns = [];
|
|
3452
3515
|
_.forEach(field.cols, (val) => {
|
|
@@ -3807,14 +3870,19 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3807
3870
|
const isBEEAppEnableForCapTagList = this.checkBeeEditorAllowedForLibrary();
|
|
3808
3871
|
ifError = this.state.checkValidation && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
|
|
3809
3872
|
|
|
3810
|
-
//
|
|
3873
|
+
// Pre-header hidden behind "+ Pre-header" unless it has content or the user toggled it.
|
|
3874
|
+
if (val.id === 'template-preheader' && !isPreheaderRevealed) {
|
|
3875
|
+
break;
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
// Subject/Pre-header render even in BEE mode
|
|
3811
3879
|
if (
|
|
3812
|
-
val.id
|
|
3880
|
+
HIGH_FREQ_EMAIL_TAG_INPUT_IDS.includes(val.id) ||
|
|
3813
3881
|
!(_.get(this.state, `formData[${this.state.currentTab - 1}][${currentLangForCapTagList}].is_drag_drop`, false)) ||
|
|
3814
3882
|
isBEEAppEnableForCapTagList === false ||
|
|
3815
3883
|
channelForCapTagList !== 'EMAIL'
|
|
3816
3884
|
) {
|
|
3817
|
-
const isEmailStandaloneSubject = val.standalone && channelForCapTagList === EMAIL && val.id
|
|
3885
|
+
const isEmailStandaloneSubject = val.standalone && channelForCapTagList === EMAIL && HIGH_FREQ_EMAIL_TAG_INPUT_IDS.includes(val.id);
|
|
3818
3886
|
const tagListProps = {
|
|
3819
3887
|
key: `input-${val.id}`,
|
|
3820
3888
|
inputId: val.id,
|
|
@@ -3823,9 +3891,12 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3823
3891
|
inputErrorMessage: val.errorMessage && ifError ? val.errorMessage : '',
|
|
3824
3892
|
inputRequired: val.required || false,
|
|
3825
3893
|
inputDisabled: val.disabled || false,
|
|
3894
|
+
inputMaxLength: val.maxLength,
|
|
3826
3895
|
headingText: val.label || '',
|
|
3827
3896
|
headingStyle: val.headingStyle || { marginTop: '3%', marginRight: '79%' },
|
|
3828
3897
|
headingType: "h4",
|
|
3898
|
+
// Label is an inline input prefix (see inputProps), so no heading here.
|
|
3899
|
+
showHeading: !isEmailStandaloneSubject,
|
|
3829
3900
|
onTagSelect: (data) => this.callChildEvent(data, val, 'onTagSelect'),
|
|
3830
3901
|
onContextChange: this.props.onContextChange,
|
|
3831
3902
|
location: this.props.location,
|
|
@@ -3838,10 +3909,42 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3838
3909
|
waitEventContextTags: this.props?.waitEventContextTags,
|
|
3839
3910
|
moduleFilterEnabled: moduleFilterEnabledForCapTagList,
|
|
3840
3911
|
containerStyle: val.style || {},
|
|
3841
|
-
inputProps:
|
|
3912
|
+
inputProps: isEmailStandaloneSubject
|
|
3913
|
+
? {
|
|
3914
|
+
...(val.inputProps || {}),
|
|
3915
|
+
prefix: (
|
|
3916
|
+
<CapLabelInline type="label2" className="high-freq-tag-input-prefix-label">
|
|
3917
|
+
{val.label || ''}
|
|
3918
|
+
</CapLabelInline>
|
|
3919
|
+
),
|
|
3920
|
+
}
|
|
3921
|
+
: (val.inputProps || {}),
|
|
3842
3922
|
showInput: val.showInput !== false,
|
|
3843
3923
|
showTagList: val.showTagList !== false,
|
|
3844
3924
|
restrictPersonalization: this.props.restrictPersonalization,
|
|
3925
|
+
...(val.id === 'template-subject' && !isPreheaderRevealed ? {
|
|
3926
|
+
extraContent: (
|
|
3927
|
+
<CapButton
|
|
3928
|
+
type="dashed"
|
|
3929
|
+
size="small"
|
|
3930
|
+
style={{ whiteSpace: 'nowrap' }}
|
|
3931
|
+
onClick={() => this.setState({ preheaderRevealed: true })}
|
|
3932
|
+
>
|
|
3933
|
+
{this.props.intl.formatMessage(messages.addPreheader)}
|
|
3934
|
+
</CapButton>
|
|
3935
|
+
),
|
|
3936
|
+
} : {}),
|
|
3937
|
+
...(val.id === 'template-preheader' ? {
|
|
3938
|
+
onRemove: () => {
|
|
3939
|
+
this.performFormDataUpdate('', val);
|
|
3940
|
+
this.setState({ preheaderRevealed: false });
|
|
3941
|
+
},
|
|
3942
|
+
removeLabel: this.props.intl.formatMessage(messages.removePreheader),
|
|
3943
|
+
// Unsubscribe is not a valid Pre-header tag — hide it from the Add Label dropdown.
|
|
3944
|
+
excludeTags: [UNSUBSCRIBE_TAG],
|
|
3945
|
+
// New ref per Save/Done click; lets HighFreqTagInput hide stale errors while editing.
|
|
3946
|
+
errorDataRef: this.state.errorData,
|
|
3947
|
+
} : {}),
|
|
3845
3948
|
};
|
|
3846
3949
|
columns.push(
|
|
3847
3950
|
<CapColumn key={`input-${val.id}`} offset={val.offset} span={val.width ? val.width : ''} style={val.style ? val.style : {marginBottom: '16px'}}>
|
|
@@ -4249,7 +4352,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
4249
4352
|
fields.push(row);
|
|
4250
4353
|
}
|
|
4251
4354
|
});
|
|
4252
|
-
if (!this.props.isEmailLoading && this.props.schema.channel.toUpperCase() === EMAIL && !fields.length) {
|
|
4355
|
+
if (!this.props.isEmailLoading && this.props.schema.channel.toUpperCase() === EMAIL && !fields.length && !isHiddenPreheaderOnlySection) {
|
|
4253
4356
|
return (
|
|
4254
4357
|
<div style={{ textAlign: 'center' }}>
|
|
4255
4358
|
<CapSpin spinning />
|
|
@@ -136,4 +136,22 @@
|
|
|
136
136
|
}
|
|
137
137
|
.error-boundary{
|
|
138
138
|
border: $CAP_SPACE_02 solid $CAP_RED;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Suffix controls (character counter, Remove link) rendered alongside EMAIL's
|
|
142
|
+
// Subject/Pre-header inputs by HighFreqTagInput (Classic.js).
|
|
143
|
+
.high-freq-tag-input-counter {
|
|
144
|
+
white-space: nowrap;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.high-freq-tag-input-remove-link {
|
|
148
|
+
white-space: nowrap;
|
|
149
|
+
cursor: pointer;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.high-freq-tag-input-prefix-label {
|
|
153
|
+
font-weight: 600;
|
|
154
|
+
padding-right: $CAP_SPACE_08;
|
|
155
|
+
margin-right: $CAP_SPACE_08;
|
|
156
|
+
border-right: 1px solid $CAP_COLOR_16;
|
|
139
157
|
}
|
|
@@ -98,4 +98,12 @@ export default defineMessages({
|
|
|
98
98
|
id: `creatives.componentsV2.FormBuilder.personalizationTagsErrorMessage`,
|
|
99
99
|
defaultMessage: 'Personalization tags are not supported for anonymous customers, please remove the tags.',
|
|
100
100
|
},
|
|
101
|
+
addPreheader: {
|
|
102
|
+
id: 'creatives.componentsV2.FormBuilder.addPreheader',
|
|
103
|
+
defaultMessage: '+ Pre-header',
|
|
104
|
+
},
|
|
105
|
+
removePreheader: {
|
|
106
|
+
id: 'creatives.componentsV2.FormBuilder.removePreheader',
|
|
107
|
+
defaultMessage: 'Remove',
|
|
108
|
+
},
|
|
101
109
|
});
|
|
@@ -8,6 +8,7 @@ import { shallowWithIntl } from '../../../helpers/intl-enzym-test-helpers';
|
|
|
8
8
|
const { match, location, capGlobal, language } = mockData;
|
|
9
9
|
|
|
10
10
|
jest.mock('@capillarytech/cap-ui-utils', () => ({
|
|
11
|
+
getEnvVariable: jest.fn(),
|
|
11
12
|
GA: {
|
|
12
13
|
initialize: jest.fn(),
|
|
13
14
|
timeTracker: {
|
|
@@ -479,7 +479,7 @@ describe('handleSave — CCS flow', () => {
|
|
|
479
479
|
initialData: {
|
|
480
480
|
contentItems: [{
|
|
481
481
|
channel: 'EMAIL',
|
|
482
|
-
templateData: { emailSubject: 'Order Confirmed', emailBody: '<html>Hi</html>' },
|
|
482
|
+
templateData: { emailSubject: 'Order Confirmed', emailBody: '<html>Hi</html>', emailPreHeader: 'Your order is on its way' },
|
|
483
483
|
}],
|
|
484
484
|
},
|
|
485
485
|
onSave,
|
|
@@ -497,6 +497,7 @@ describe('handleSave — CCS flow', () => {
|
|
|
497
497
|
channel: 'EMAIL',
|
|
498
498
|
messageSubject: 'Order Confirmed',
|
|
499
499
|
messageBody: '<html>Hi</html>',
|
|
500
|
+
messagePreHeader: 'Your order is on its way',
|
|
500
501
|
},
|
|
501
502
|
}),
|
|
502
503
|
}),
|
|
@@ -10,20 +10,20 @@ describe('CCS_CONTENT_TRANSFORMS', () => {
|
|
|
10
10
|
it('prefers emailBody over emailHtml when both are present', () => {
|
|
11
11
|
expect(
|
|
12
12
|
CCS_CONTENT_TRANSFORMS.EMAIL({
|
|
13
|
-
emailSubject: 'Subject', emailBody: '<p>Body</p>', emailHtml: '<p>Html</p>',
|
|
13
|
+
emailSubject: 'Subject', emailBody: '<p>Body</p>', emailHtml: '<p>Html</p>', emailPreHeader: 'Preheader',
|
|
14
14
|
}),
|
|
15
|
-
).toEqual({ messageSubject: 'Subject', messageBody: '<p>Body</p>' });
|
|
15
|
+
).toEqual({ messageSubject: 'Subject', messageBody: '<p>Body</p>', messagePreHeader: 'Preheader' });
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
it('falls back to emailHtml when emailBody is absent', () => {
|
|
19
19
|
expect(
|
|
20
20
|
CCS_CONTENT_TRANSFORMS.EMAIL({ emailSubject: 'Subject', emailHtml: '<p>Html</p>' }),
|
|
21
|
-
).toEqual({ messageSubject: 'Subject', messageBody: '<p>Html</p>' });
|
|
21
|
+
).toEqual({ messageSubject: 'Subject', messageBody: '<p>Html</p>', messagePreHeader: undefined });
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
it('defaults templateData to {} when called with no argument', () => {
|
|
25
25
|
expect(CCS_CONTENT_TRANSFORMS.EMAIL()).toEqual({
|
|
26
|
-
messageSubject: undefined, messageBody: undefined,
|
|
26
|
+
messageSubject: undefined, messageBody: undefined, messagePreHeader: undefined,
|
|
27
27
|
});
|
|
28
28
|
});
|
|
29
29
|
});
|
|
@@ -65,6 +65,7 @@ export const CCS_CONTENT_TRANSFORMS = {
|
|
|
65
65
|
EMAIL: (templateData = {}) => ({
|
|
66
66
|
messageSubject: templateData.emailSubject,
|
|
67
67
|
messageBody: templateData.emailBody || templateData.emailHtml,
|
|
68
|
+
messagePreHeader: templateData.emailPreHeader,
|
|
68
69
|
}),
|
|
69
70
|
SMS: (templateData = {}) => ({
|
|
70
71
|
message: templateData.messageBody || '',
|
|
@@ -101,7 +101,7 @@ describe('mapCommDefinitionToStepData', () => {
|
|
|
101
101
|
singleChannelStrategy: {
|
|
102
102
|
variant: {
|
|
103
103
|
channel: 'EMAIL',
|
|
104
|
-
emailMessageContent: { messageSubject: 'Subject', messageBody: '<p>Body</p>' },
|
|
104
|
+
emailMessageContent: { messageSubject: 'Subject', messageBody: '<p>Body</p>', messagePreHeader: 'Preheader' },
|
|
105
105
|
},
|
|
106
106
|
},
|
|
107
107
|
};
|
|
@@ -111,6 +111,7 @@ describe('mapCommDefinitionToStepData', () => {
|
|
|
111
111
|
expect(result.contentItems[0].templateData).toEqual({
|
|
112
112
|
emailSubject: 'Subject',
|
|
113
113
|
emailBody: '<p>Body</p>',
|
|
114
|
+
emailPreHeader: 'Preheader',
|
|
114
115
|
channel: 'EMAIL',
|
|
115
116
|
});
|
|
116
117
|
});
|
|
@@ -27,6 +27,7 @@ const CCS_CONTENT_REVERSE_TRANSFORMS = {
|
|
|
27
27
|
EMAIL: (payload = {}) => ({
|
|
28
28
|
emailSubject: payload.messageSubject,
|
|
29
29
|
emailBody: payload.messageBody,
|
|
30
|
+
emailPreHeader: payload.messagePreHeader,
|
|
30
31
|
}),
|
|
31
32
|
SMS: (payload = {}) => ({
|
|
32
33
|
messageBody: payload.message || '',
|
|
@@ -320,7 +320,7 @@ export class Creatives extends React.Component {
|
|
|
320
320
|
};
|
|
321
321
|
|
|
322
322
|
onEditTemplate = () => {
|
|
323
|
-
if (
|
|
323
|
+
if (this.props.templateData?.isArchived) {
|
|
324
324
|
CapNotification.error({ message: this.props.intl.formatMessage(messages.cannotEditArchivedTemplate) });
|
|
325
325
|
return;
|
|
326
326
|
}
|
|
@@ -503,6 +503,7 @@ export class Creatives extends React.Component {
|
|
|
503
503
|
case constants.EMAIL: {
|
|
504
504
|
const formData = {
|
|
505
505
|
subject: templateData.emailSubject,
|
|
506
|
+
preheader: templateData.emailPreHeader,
|
|
506
507
|
html_content: templateData.emailBody,
|
|
507
508
|
is_drag_drop: templateData.is_drag_drop,
|
|
508
509
|
lang_id: templateData.lang_id,
|
|
@@ -1092,10 +1093,15 @@ export class Creatives extends React.Component {
|
|
|
1092
1093
|
}
|
|
1093
1094
|
const newHtmlContent = await updateImagesInHtml(html_content);
|
|
1094
1095
|
templateData = {
|
|
1095
|
-
...templateData,
|
|
1096
|
+
...templateData,
|
|
1097
|
+
...emailBase,
|
|
1098
|
+
emailBody: newHtmlContent,
|
|
1099
|
+
emailSubject: (emailBase && emailBase.subject) ? emailBase.subject : '',
|
|
1100
|
+
emailPreHeader: (emailBase && emailBase.preheader) ? emailBase.preheader : '',
|
|
1096
1101
|
};
|
|
1097
1102
|
delete templateData.html_content;
|
|
1098
1103
|
delete templateData.subject;
|
|
1104
|
+
delete templateData.preheader;
|
|
1099
1105
|
}
|
|
1100
1106
|
break;
|
|
1101
1107
|
case constants.MOBILE_PUSH:
|
|
@@ -2472,7 +2478,7 @@ export class Creatives extends React.Component {
|
|
|
2472
2478
|
onSave={this.saveMessage}
|
|
2473
2479
|
onDiscard={this.discardMessage}
|
|
2474
2480
|
onEditTemplate={this.onEditTemplate}
|
|
2475
|
-
isTemplateArchived={!!(
|
|
2481
|
+
isTemplateArchived={!!(this.props.templateData && this.props.templateData.isArchived)}
|
|
2476
2482
|
slidBoxContent={slidBoxContent}
|
|
2477
2483
|
onCreateNextStep={this.onCreateNextStep}
|
|
2478
2484
|
currentChannel={currentChannel.toUpperCase()}
|
|
@@ -376,4 +376,85 @@ describe('Test SlideBoxContent container', () => {
|
|
|
376
376
|
expect(getCreativesData).toHaveBeenCalledTimes(1);
|
|
377
377
|
expect(getCreativesData).toHaveBeenCalledWith({ channel: 'EMAIL'});
|
|
378
378
|
});
|
|
379
|
+
|
|
380
|
+
it('EMAIL getFormData maps preheader to emailPreHeader for campaigns, alongside subject/body', async () => {
|
|
381
|
+
renderFunction('EMAIL', 'editTemplate', emailTemplates, emailEditTemplateData);
|
|
382
|
+
renderedComponent.instance().onEditTemplate();
|
|
383
|
+
|
|
384
|
+
const mockValue = {
|
|
385
|
+
validity: true,
|
|
386
|
+
value: {
|
|
387
|
+
name: 'email_creative_name',
|
|
388
|
+
type: 'EMAIL',
|
|
389
|
+
base: {
|
|
390
|
+
subject: 'Test subject',
|
|
391
|
+
preheader: 'Test preheader',
|
|
392
|
+
html_content: '<p>hello</p>',
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
renderedComponent.instance().getFormData(mockValue);
|
|
398
|
+
await tick();
|
|
399
|
+
|
|
400
|
+
expect(getCreativesData).toHaveBeenCalledTimes(1);
|
|
401
|
+
const sentData = getCreativesData.mock.calls[0][0];
|
|
402
|
+
expect(sentData.emailSubject).toBe('Test subject');
|
|
403
|
+
expect(sentData.emailPreHeader).toBe('Test preheader');
|
|
404
|
+
expect(sentData.emailBody).toBe('<p>hello</p>');
|
|
405
|
+
expect(sentData.subject).toBeUndefined();
|
|
406
|
+
expect(sentData.preheader).toBeUndefined();
|
|
407
|
+
expect(sentData.html_content).toBeUndefined();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('EMAIL getFormData maps emailPreHeader to an empty string when not present', async () => {
|
|
411
|
+
renderFunction('EMAIL', 'editTemplate', emailTemplates, emailEditTemplateData);
|
|
412
|
+
renderedComponent.instance().onEditTemplate();
|
|
413
|
+
|
|
414
|
+
const mockValue = {
|
|
415
|
+
validity: true,
|
|
416
|
+
value: {
|
|
417
|
+
name: 'email_creative_name',
|
|
418
|
+
type: 'EMAIL',
|
|
419
|
+
base: {
|
|
420
|
+
subject: 'Test subject',
|
|
421
|
+
html_content: '<p>hello</p>',
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
renderedComponent.instance().getFormData(mockValue);
|
|
427
|
+
await tick();
|
|
428
|
+
|
|
429
|
+
expect(getCreativesData).toHaveBeenCalledTimes(1);
|
|
430
|
+
const sentData = getCreativesData.mock.calls[0][0];
|
|
431
|
+
expect(sentData.emailPreHeader).toBe('');
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('EMAIL getTemplateData maps emailPreHeader back to preheader when reopening a saved campaign message', () => {
|
|
435
|
+
renderedComponent = shallowWithIntl(
|
|
436
|
+
<Creatives
|
|
437
|
+
loyaltyMetaData={loyaltyMetaData}
|
|
438
|
+
Templates={emailTemplates}
|
|
439
|
+
channel="EMAIL"
|
|
440
|
+
slidBoxContent="editTemplate"
|
|
441
|
+
templateData={{
|
|
442
|
+
channel: 'EMAIL',
|
|
443
|
+
emailSubject: 'Reopened subject',
|
|
444
|
+
emailPreHeader: 'Reopened preheader',
|
|
445
|
+
emailBody: '<p>hello</p>',
|
|
446
|
+
}}
|
|
447
|
+
handleCloseCreatives={handleCloseCreatives}
|
|
448
|
+
getCreativesData={getCreativesData}
|
|
449
|
+
isFullMode={false}
|
|
450
|
+
templateActions={{
|
|
451
|
+
getCdnTransformationConfig,
|
|
452
|
+
}}
|
|
453
|
+
/>,
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
const { templateData } = renderedComponent.instance().state;
|
|
457
|
+
expect(templateData.base.subject).toBe('Reopened subject');
|
|
458
|
+
expect(templateData.base.preheader).toBe('Reopened preheader');
|
|
459
|
+
});
|
|
379
460
|
});
|
|
@@ -119,6 +119,10 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
119
119
|
"template-subject": {
|
|
120
120
|
onTagSelect: this.onTagSelect,
|
|
121
121
|
},
|
|
122
|
+
// unified pre-header field with input and tag selection
|
|
123
|
+
"template-preheader": {
|
|
124
|
+
onTagSelect: this.onTagSelect,
|
|
125
|
+
},
|
|
122
126
|
"template-version": {
|
|
123
127
|
onSelect: this.handleVersionSelect,
|
|
124
128
|
addVersion: this.addVersion,
|
|
@@ -645,6 +649,14 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
645
649
|
formData['template-subject'] = subjectFromEditData;
|
|
646
650
|
}
|
|
647
651
|
}
|
|
652
|
+
|
|
653
|
+
// Ensure template-preheader is preserved at the top level
|
|
654
|
+
if (formData['template-preheader'] === undefined || formData['template-preheader'] === '') {
|
|
655
|
+
const preheaderFromEditData = _.get(nextProps, 'Email.templateDetails.versions.base.preheader', '');
|
|
656
|
+
if (preheaderFromEditData) {
|
|
657
|
+
formData['template-preheader'] = preheaderFromEditData;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
648
660
|
|
|
649
661
|
if (langIndex !== undefined && langIndex !== -1 && temp && temp.panes) {
|
|
650
662
|
_.forEach(temp.panes, (pane, index) => {
|
|
@@ -903,11 +915,11 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
903
915
|
}
|
|
904
916
|
|
|
905
917
|
onTagSelect = (data, currentTab, sourceVal) => {
|
|
906
|
-
// If tag is selected from Subject's TagList
|
|
907
|
-
if (sourceVal && (sourceVal.id === 'title-tagList' || sourceVal.id === 'template-subject')) {
|
|
918
|
+
// If tag is selected from Subject's TagList, unified subject field, or the pre-header field, insert into the relevant input
|
|
919
|
+
if (sourceVal && (sourceVal.id === 'title-tagList' || sourceVal.id === 'template-subject' || sourceVal.id === 'template-preheader')) {
|
|
908
920
|
const tagToInsert = `{{${data}}}`;
|
|
909
921
|
const formData = _.cloneDeep(this.state.formData);
|
|
910
|
-
const fieldId = 'template-subject';
|
|
922
|
+
const fieldId = sourceVal.id === 'template-preheader' ? 'template-preheader' : 'template-subject';
|
|
911
923
|
const input = document.getElementById(fieldId) || document.querySelector(`#${fieldId} input`);
|
|
912
924
|
let subjectValue = formData[fieldId] || '';
|
|
913
925
|
try {
|
|
@@ -1184,6 +1196,8 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
1184
1196
|
formData['template-name'] = editData.name;
|
|
1185
1197
|
const subject = _.get(editData, 'versions.base.subject', '');
|
|
1186
1198
|
formData['template-subject'] = subject;
|
|
1199
|
+
const preheader = _.get(editData, 'versions.base.preheader', '');
|
|
1200
|
+
formData['template-preheader'] = preheader;
|
|
1187
1201
|
formData.base = editData.versions.base;
|
|
1188
1202
|
|
|
1189
1203
|
_.forEach(editData.versions.history, (data1, index) => {
|
|
@@ -1686,6 +1700,7 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
1686
1700
|
newFormData.base = _.cloneDeep(tmpData);
|
|
1687
1701
|
newFormData.secondary_templates.push({template_data: tmpData});
|
|
1688
1702
|
newFormData.base.subject = formData['template-subject'];
|
|
1703
|
+
newFormData.base.preheader = formData['template-preheader'] || '';
|
|
1689
1704
|
return newFormData;
|
|
1690
1705
|
}
|
|
1691
1706
|
|
|
@@ -2531,7 +2546,14 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
2531
2546
|
newSchema.standalone.sections = [];
|
|
2532
2547
|
if (this.props.location.query.type === 'embedded' && (this.props.location.query.module === "loyalty" || this.props.location.query.module === "dvs" || this.props.location.query.module === "coupon_expiry" || this.props.location.query.module === "timeline" ||
|
|
2533
2548
|
this.props.location.query.module === "library")) {
|
|
2534
|
-
|
|
2549
|
+
// Keep only Subject/Pre-header (matched by field id; template-name stays hidden).
|
|
2550
|
+
const standAloneFieldIdsToKeep = ['template-subject', 'template-preheader'];
|
|
2551
|
+
_.forEach(_.get(schema, 'standalone.sections', []), (section) => {
|
|
2552
|
+
const hasKeepableField = _.some(section.inputFields, (inputField) => _.some(inputField.cols, (col) => standAloneFieldIdsToKeep.includes(col.id)));
|
|
2553
|
+
if (hasKeepableField) {
|
|
2554
|
+
newSchema.standalone.sections.push(_.cloneDeep(section));
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2535
2557
|
}
|
|
2536
2558
|
|
|
2537
2559
|
_.forEach(newSchema.containers, (container) => {
|
|
@@ -2590,6 +2612,7 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
2590
2612
|
if ((this.props.location.query.isLanguageSupport === undefined || this.props.location.query.isLanguageSupport === 'false')) {
|
|
2591
2613
|
// If formData['template-subject'] is empty, assign an empty string; otherwise, retain its current value or use data.base.subject.
|
|
2592
2614
|
formData['template-subject'] = formData?.['template-subject'] === '' ? '' : (data?.base?.subject || '');
|
|
2615
|
+
formData['template-preheader'] = formData?.['template-preheader'] === '' ? '' : (data?.base?.preheader || '');
|
|
2593
2616
|
const baseLanguage = this.props.currentOrgDetails.basic_details.base_language;
|
|
2594
2617
|
let languageIndex = _.findIndex(this.supportedLanguages, {iso_code: baseLanguage});
|
|
2595
2618
|
|
|
@@ -2923,12 +2946,16 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
2923
2946
|
if (index === "template-subject" && obj?.versions?.history?.[0]) {
|
|
2924
2947
|
obj.versions.history[0].subject = newdata;
|
|
2925
2948
|
}
|
|
2949
|
+
if (index === "template-preheader" && obj?.versions?.history?.[0]) {
|
|
2950
|
+
obj.versions.history[0].preheader = newdata;
|
|
2951
|
+
}
|
|
2926
2952
|
});
|
|
2927
2953
|
//const data = formData[`${this.state.currentTab - 1}`];
|
|
2928
2954
|
obj.name = formData['template-name'];
|
|
2929
2955
|
obj.versions.base = formData.base;
|
|
2930
2956
|
obj.versions.base.tabKey = baseKey;
|
|
2931
2957
|
obj.versions.base.subject = formData['template-subject'];
|
|
2958
|
+
obj.versions.base.preheader = formData['template-preheader'] || '';
|
|
2932
2959
|
obj.versions.base.activeTab = formData?.base?.activeTab || baseLanguage;
|
|
2933
2960
|
_.forEach(obj.versions.base.selectedLanguages, (language) => {
|
|
2934
2961
|
if (obj.versions.base[language].is_drag_drop) {
|
|
@@ -42,6 +42,36 @@ export const response = {
|
|
|
42
42
|
},
|
|
43
43
|
],
|
|
44
44
|
},
|
|
45
|
+
{
|
|
46
|
+
type: "multicols",
|
|
47
|
+
inputFields: [
|
|
48
|
+
{
|
|
49
|
+
cols: [
|
|
50
|
+
{
|
|
51
|
+
id: "template-preheader",
|
|
52
|
+
placeholder: "Enter Pre-header Text",
|
|
53
|
+
type: "cap-tag-list-with-input",
|
|
54
|
+
metaType: "text",
|
|
55
|
+
datatype: "string",
|
|
56
|
+
required: false,
|
|
57
|
+
label: "Pre-header",
|
|
58
|
+
styling: "semantic",
|
|
59
|
+
order: 1,
|
|
60
|
+
width: 16,
|
|
61
|
+
customComponent: true,
|
|
62
|
+
standalone: true,
|
|
63
|
+
onlyDisplay: false,
|
|
64
|
+
maxLength: 150,
|
|
65
|
+
errorMessage: "Unsubscribe tag is not allowed in Pre-header.",
|
|
66
|
+
supportedEvents: [
|
|
67
|
+
"onChange",
|
|
68
|
+
"onTagSelect",
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
45
75
|
{
|
|
46
76
|
type: "multicols",
|
|
47
77
|
inputFields: [
|
|
@@ -154,6 +154,14 @@ export default defineMessages({
|
|
|
154
154
|
id: 'creatives.containersV2.Email.emailSubjectNotEmplty',
|
|
155
155
|
defaultMessage: 'Email subject cannot be empty.',
|
|
156
156
|
},
|
|
157
|
+
"Enter Pre-header Text": {
|
|
158
|
+
id: 'creatives.containersV2.Email.emailPreheader',
|
|
159
|
+
defaultMessage: 'Enter pre-header text',
|
|
160
|
+
},
|
|
161
|
+
"Pre-header": {
|
|
162
|
+
id: 'creatives.containersV2.Email.Preheader',
|
|
163
|
+
defaultMessage: 'Pre-header',
|
|
164
|
+
},
|
|
157
165
|
"Enter template name": {
|
|
158
166
|
id: 'creatives.containersV2.Email.templateNamePlaceholder',
|
|
159
167
|
defaultMessage: 'Enter template name',
|