@capillarytech/creatives-library 9.0.73-alpha.sprint-testpreview.22 → 9.1.1
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/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 +36 -75
- 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 +92 -97
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +24 -0
- package/v2Components/CommonTestAndPreview/tests/utils.test.js +0 -66
- package/v2Components/CommonTestAndPreview/utils.js +0 -14
- 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 +37 -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 +134 -14
- package/v2Components/FormBuilder/Functional/FormBuilderShell.js +9 -0
- package/v2Components/FormBuilder/Functional/layout/TabsContainer.js +11 -12
- package/v2Components/FormBuilder/Functional/tests/mpush.singlePlatform.test.js +144 -0
- package/v2Components/FormBuilder/Functional/tests/tabsContainer.test.js +17 -5
- package/v2Components/FormBuilder/_formBuilder.scss +21 -0
- package/v2Components/FormBuilder/messages.js +12 -0
- package/v2Components/FormBuilder/tests/mpush.characterization.test.js +45 -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/SlideBoxContent.js +0 -2
- package/v2Containers/CreativesContainer/index.js +7 -1
- 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 +172 -8
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +264 -5
- package/v2Containers/EmailWrapper/components/_emailHTMLEditor.scss +29 -0
- package/v2Containers/EmailWrapper/constants.js +5 -0
- package/v2Containers/EmailWrapper/messages.js +24 -0
- package/v2Containers/MobilePush/Create/constants.js +1 -0
- package/v2Containers/MobilePush/Create/index.js +2 -2
- package/v2Containers/MobilePush/Create/test/contentValidity.test.js +58 -0
- package/v2Containers/MobilePush/Edit/index.js +2 -2
- package/v2Containers/MobilePush/Edit/test/contentValidity.test.js +17 -0
- package/v2Containers/MobilePush/commonMethods.js +7 -8
- package/v2Containers/Rcs/index.js +1 -27
- package/v2Containers/TagList/index.js +7 -0
- package/v2Containers/TagList/tests/TagList.test.js +42 -0
- package/v2Containers/Templates/index.js +4 -9
- package/v2Containers/Templates/tests/index.test.js +35 -107
- package/v2Containers/Viber/index.scss +31 -0
- package/v2Containers/Whatsapp/index.js +1 -18
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +2 -2
- package/v2Containers/Zalo/index.js +0 -18
- package/v2Containers/Rcs/tests/testAndPreviewFromListing.test.js +0 -132
- package/v2Containers/Whatsapp/tests/testAndPreviewFromListing.test.js +0 -91
- package/v2Containers/Zalo/tests/testAndPreviewFromListing.test.js +0 -98
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import { IntlProvider } from 'react-intl';
|
|
|
5
5
|
import CapTagListWithInput from '../index';
|
|
6
6
|
|
|
7
7
|
const capturedTagListProps = { current: null };
|
|
8
|
+
const capturedCapRowProps = { current: null };
|
|
8
9
|
|
|
9
10
|
jest.mock('../../../v2Containers/TagList', () => {
|
|
10
11
|
const React = require('react');
|
|
@@ -15,7 +16,14 @@ jest.mock('../../../v2Containers/TagList', () => {
|
|
|
15
16
|
return Mock;
|
|
16
17
|
});
|
|
17
18
|
|
|
18
|
-
jest.mock('@capillarytech/cap-ui-library/CapRow', () =>
|
|
19
|
+
jest.mock('@capillarytech/cap-ui-library/CapRow', () => {
|
|
20
|
+
const React = require('react');
|
|
21
|
+
const Mock = ({ children, ...rest }) => {
|
|
22
|
+
capturedCapRowProps.current = rest;
|
|
23
|
+
return <div>{children}</div>;
|
|
24
|
+
};
|
|
25
|
+
return Mock;
|
|
26
|
+
});
|
|
19
27
|
jest.mock('@capillarytech/cap-ui-library/CapColumn', () => ({ children }) => <div>{children}</div>);
|
|
20
28
|
jest.mock('@capillarytech/cap-ui-library/CapHeading', () => () => null);
|
|
21
29
|
jest.mock('@capillarytech/cap-ui-library/CapInput', () => () => <input data-testid="cap-input" />);
|
|
@@ -27,6 +35,7 @@ const waitMap = {
|
|
|
27
35
|
describe('CapTagListWithInput', () => {
|
|
28
36
|
beforeEach(() => {
|
|
29
37
|
capturedTagListProps.current = null;
|
|
38
|
+
capturedCapRowProps.current = null;
|
|
30
39
|
});
|
|
31
40
|
|
|
32
41
|
it('forwards waitEventContextTags to TagList', () => {
|
|
@@ -60,4 +69,52 @@ describe('CapTagListWithInput', () => {
|
|
|
60
69
|
|
|
61
70
|
expect(capturedTagListProps.current.waitEventContextTags).toEqual({});
|
|
62
71
|
});
|
|
72
|
+
|
|
73
|
+
it('forwards excludeTags to TagList', () => {
|
|
74
|
+
render(
|
|
75
|
+
<IntlProvider locale="en" messages={{}}>
|
|
76
|
+
<CapTagListWithInput
|
|
77
|
+
inputId="template-preheader"
|
|
78
|
+
inputOnChange={jest.fn()}
|
|
79
|
+
excludeTags={['unsubscribe']}
|
|
80
|
+
onTagSelect={jest.fn()}
|
|
81
|
+
onContextChange={jest.fn()}
|
|
82
|
+
/>
|
|
83
|
+
</IntlProvider>
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
expect(capturedTagListProps.current.excludeTags).toEqual(['unsubscribe']);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('uses default empty array for excludeTags when omitted', () => {
|
|
90
|
+
render(
|
|
91
|
+
<IntlProvider locale="en" messages={{}}>
|
|
92
|
+
<CapTagListWithInput
|
|
93
|
+
inputId="test-url"
|
|
94
|
+
inputOnChange={jest.fn()}
|
|
95
|
+
onTagSelect={jest.fn()}
|
|
96
|
+
onContextChange={jest.fn()}
|
|
97
|
+
/>
|
|
98
|
+
</IntlProvider>
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(capturedTagListProps.current.excludeTags).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('top-aligns the singleLineLayout row so an error message growing the input does not shift the Add Label button', () => {
|
|
105
|
+
render(
|
|
106
|
+
<IntlProvider locale="en" messages={{}}>
|
|
107
|
+
<CapTagListWithInput
|
|
108
|
+
inputId="template-preheader"
|
|
109
|
+
inputOnChange={jest.fn()}
|
|
110
|
+
inputErrorMessage="Unsubscribe tag is not allowed in Pre-header."
|
|
111
|
+
singleLineLayout
|
|
112
|
+
onTagSelect={jest.fn()}
|
|
113
|
+
onContextChange={jest.fn()}
|
|
114
|
+
/>
|
|
115
|
+
</IntlProvider>
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(capturedCapRowProps.current.align).toBe('top');
|
|
119
|
+
});
|
|
63
120
|
});
|
|
@@ -7,6 +7,7 @@ import CapInput from '@capillarytech/cap-ui-library/CapInput';
|
|
|
7
7
|
import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
|
|
8
8
|
import TagList from '../../v2Containers/TagList';
|
|
9
9
|
import messages from './messages';
|
|
10
|
+
import './index.scss';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* CapTagListWithInput - A reusable component that combines TagList and CapInput
|
|
@@ -18,6 +19,7 @@ export const CapTagListWithInput = (props) => {
|
|
|
18
19
|
// TagList props
|
|
19
20
|
tags = [],
|
|
20
21
|
injectedTags = {},
|
|
22
|
+
excludeTags = [],
|
|
21
23
|
location = {},
|
|
22
24
|
selectedOfferDetails = [],
|
|
23
25
|
onTagSelect,
|
|
@@ -51,10 +53,65 @@ export const CapTagListWithInput = (props) => {
|
|
|
51
53
|
showInput = true,
|
|
52
54
|
inputProps = {},
|
|
53
55
|
popoverPlacement,
|
|
56
|
+
// Tag-insert trigger inline with the input (not in a row above)
|
|
57
|
+
singleLineLayout = false,
|
|
58
|
+
// Content between input and tag trigger (singleLineLayout only)
|
|
59
|
+
extraContent = null,
|
|
54
60
|
} = props;
|
|
55
61
|
|
|
56
62
|
const { formatMessage } = intl;
|
|
57
63
|
|
|
64
|
+
const tagListElement = 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
|
+
excludeTags={excludeTags}
|
|
76
|
+
userLocale={userLocale}
|
|
77
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
78
|
+
eventContextTags={eventContextTags}
|
|
79
|
+
waitEventContextTags={waitEventContextTags}
|
|
80
|
+
style={tagListStyle}
|
|
81
|
+
popoverPlacement={popoverPlacement}
|
|
82
|
+
restrictPersonalization={restrictPersonalization}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const inputElement = showInput && (
|
|
87
|
+
<CapInput
|
|
88
|
+
id={inputId}
|
|
89
|
+
onChange={inputOnChange}
|
|
90
|
+
placeholder={inputPlaceholder}
|
|
91
|
+
value={inputValue}
|
|
92
|
+
size={inputSize}
|
|
93
|
+
maxLength={inputMaxLength}
|
|
94
|
+
errorMessage={inputErrorMessage}
|
|
95
|
+
isRequired={inputRequired}
|
|
96
|
+
disabled={inputDisabled}
|
|
97
|
+
style={inputStyle}
|
|
98
|
+
{...inputProps}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (singleLineLayout) {
|
|
103
|
+
return (
|
|
104
|
+
<CapColumn style={containerStyle}>
|
|
105
|
+
{/* "top", not "middle": keeps the buttons in place when an error grows the input */}
|
|
106
|
+
<CapRow align="top" type="flex" noWrap gap={8} className="cap-tag-list-with-input__single-line-row">
|
|
107
|
+
<CapColumn className="cap-tag-list-with-input__input-column">{inputElement}</CapColumn>
|
|
108
|
+
{extraContent}
|
|
109
|
+
{tagListElement}
|
|
110
|
+
</CapRow>
|
|
111
|
+
</CapColumn>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
58
115
|
return (
|
|
59
116
|
<CapColumn style={containerStyle}>
|
|
60
117
|
<CapRow align="middle" type="flex" noWrap>
|
|
@@ -64,42 +121,9 @@ export const CapTagListWithInput = (props) => {
|
|
|
64
121
|
</CapHeading>
|
|
65
122
|
)}
|
|
66
123
|
|
|
67
|
-
{
|
|
68
|
-
<TagList
|
|
69
|
-
key={`${inputId}_tags`}
|
|
70
|
-
className={className}
|
|
71
|
-
moduleFilterEnabled={moduleFilterEnabled}
|
|
72
|
-
label={formatMessage(messages.addLabels)}
|
|
73
|
-
onTagSelect={onTagSelect}
|
|
74
|
-
onContextChange={onContextChange}
|
|
75
|
-
location={location}
|
|
76
|
-
tags={tags}
|
|
77
|
-
injectedTags={injectedTags}
|
|
78
|
-
userLocale={userLocale}
|
|
79
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
80
|
-
eventContextTags={eventContextTags}
|
|
81
|
-
waitEventContextTags={waitEventContextTags}
|
|
82
|
-
style={tagListStyle}
|
|
83
|
-
popoverPlacement={popoverPlacement}
|
|
84
|
-
restrictPersonalization={restrictPersonalization}
|
|
85
|
-
/>
|
|
86
|
-
)}
|
|
124
|
+
{tagListElement}
|
|
87
125
|
</CapRow>
|
|
88
|
-
{
|
|
89
|
-
<CapInput
|
|
90
|
-
id={inputId}
|
|
91
|
-
onChange={inputOnChange}
|
|
92
|
-
placeholder={inputPlaceholder}
|
|
93
|
-
value={inputValue}
|
|
94
|
-
size={inputSize}
|
|
95
|
-
maxLength={inputMaxLength}
|
|
96
|
-
errorMessage={inputErrorMessage}
|
|
97
|
-
isRequired={inputRequired}
|
|
98
|
-
disabled={inputDisabled}
|
|
99
|
-
style={inputStyle}
|
|
100
|
-
{...inputProps}
|
|
101
|
-
/>
|
|
102
|
-
)}
|
|
126
|
+
{inputElement}
|
|
103
127
|
</CapColumn>
|
|
104
128
|
);
|
|
105
129
|
};
|
|
@@ -109,6 +133,7 @@ CapTagListWithInput.propTypes = {
|
|
|
109
133
|
// TagList props
|
|
110
134
|
tags: PropTypes.array,
|
|
111
135
|
injectedTags: PropTypes.object,
|
|
136
|
+
excludeTags: PropTypes.array,
|
|
112
137
|
location: PropTypes.object,
|
|
113
138
|
selectedOfferDetails: PropTypes.array,
|
|
114
139
|
onTagSelect: PropTypes.func.isRequired,
|
|
@@ -145,6 +170,8 @@ CapTagListWithInput.propTypes = {
|
|
|
145
170
|
showTagList: PropTypes.bool,
|
|
146
171
|
showInput: PropTypes.bool,
|
|
147
172
|
popoverPlacement: PropTypes.string,
|
|
173
|
+
singleLineLayout: PropTypes.bool,
|
|
174
|
+
extraContent: PropTypes.node,
|
|
148
175
|
};
|
|
149
176
|
|
|
150
177
|
CapTagListWithInput.defaultProps = {
|
|
@@ -176,6 +203,8 @@ CapTagListWithInput.defaultProps = {
|
|
|
176
203
|
showInput: true,
|
|
177
204
|
inputProps: {},
|
|
178
205
|
popoverPlacement: undefined,
|
|
206
|
+
singleLineLayout: false,
|
|
207
|
+
extraContent: null,
|
|
179
208
|
};
|
|
180
209
|
|
|
181
210
|
export default injectIntl(CapTagListWithInput);
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
const EmailPreviewContent = ({
|
|
23
23
|
content,
|
|
24
24
|
subject,
|
|
25
|
+
preheader,
|
|
25
26
|
device,
|
|
26
27
|
isUpdating,
|
|
27
28
|
error,
|
|
@@ -75,6 +76,11 @@ const EmailPreviewContent = ({
|
|
|
75
76
|
</CapRow>
|
|
76
77
|
</CapRow>
|
|
77
78
|
|
|
79
|
+
{/* Email Pre-header */}
|
|
80
|
+
{preheader && (
|
|
81
|
+
<CapRow useLegacy className="email-preheader">{preheader}</CapRow>
|
|
82
|
+
)}
|
|
83
|
+
|
|
78
84
|
{/* Email Content - Varies based on state */}
|
|
79
85
|
<CapRow useLegacy className="email-content">
|
|
80
86
|
{contentSection}
|
|
@@ -131,6 +137,7 @@ const EmailPreviewContent = ({
|
|
|
131
137
|
EmailPreviewContent.propTypes = {
|
|
132
138
|
content: PropTypes.string,
|
|
133
139
|
subject: PropTypes.string,
|
|
140
|
+
preheader: PropTypes.string,
|
|
134
141
|
device: PropTypes.oneOf([DESKTOP, MOBILE, TABLET]),
|
|
135
142
|
isUpdating: PropTypes.bool,
|
|
136
143
|
error: PropTypes.string,
|
|
@@ -140,6 +147,7 @@ EmailPreviewContent.propTypes = {
|
|
|
140
147
|
EmailPreviewContent.defaultProps = {
|
|
141
148
|
content: '',
|
|
142
149
|
subject: '',
|
|
150
|
+
preheader: '',
|
|
143
151
|
device: DESKTOP,
|
|
144
152
|
isUpdating: false,
|
|
145
153
|
error: null,
|
|
@@ -520,6 +520,13 @@
|
|
|
520
520
|
}
|
|
521
521
|
}
|
|
522
522
|
|
|
523
|
+
// Email Pre-header
|
|
524
|
+
.email-preheader {
|
|
525
|
+
padding: $CAP_SPACE_12 $CAP_SPACE_16;
|
|
526
|
+
font-size: $FONT_SIZE_M;
|
|
527
|
+
color: #8c8c8c;
|
|
528
|
+
}
|
|
529
|
+
|
|
523
530
|
// Email Content
|
|
524
531
|
.email-content {
|
|
525
532
|
border-top: $CAP_SPACE_01 solid $CAP_G12;
|
|
@@ -43,7 +43,7 @@ export const searchCustomersRequested = (query) => ({
|
|
|
43
43
|
*/
|
|
44
44
|
export const extractTagsRequested = (payload) => ({
|
|
45
45
|
type: EXTRACT_TAGS_REQUESTED,
|
|
46
|
-
payload, // { templateSubject, templateContent }
|
|
46
|
+
payload, // { templateSubject, templatePreheader, templateContent }
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
/**
|
|
@@ -39,8 +39,6 @@ import {
|
|
|
39
39
|
mapRcsSuggestionForTestMeta,
|
|
40
40
|
getRcsPrimaryTagExtractionText,
|
|
41
41
|
mergeSyntheticMustacheTags,
|
|
42
|
-
getWhatsappTagExtractionText,
|
|
43
|
-
mergeZaloUnfilledParamTags,
|
|
44
42
|
} from './utils';
|
|
45
43
|
import AddTestCustomerButton from './AddTestCustomer';
|
|
46
44
|
import ExistingCustomerModal from './ExistingCustomerModal';
|
|
@@ -237,8 +235,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
237
235
|
const [isExtractingSmsFallbackTags, setIsExtractingSmsFallbackTags] = useState(false);
|
|
238
236
|
const [customValues, setCustomValues] = useState({});
|
|
239
237
|
const previewCustomValuesRef = useRef({});
|
|
240
|
-
// Values last applied to the preview (Update / Discard / customer prefill); drives client-side fill-in.
|
|
241
|
-
const [previewTagValues, setPreviewTagValues] = useState(null);
|
|
242
238
|
const [showJSON, setShowJSON] = useState(false);
|
|
243
239
|
const [tagsExtracted, setTagsExtracted] = useState(false);
|
|
244
240
|
|
|
@@ -599,12 +595,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
599
595
|
if (channel === CHANNELS.RCS) {
|
|
600
596
|
return mergeSyntheticMustacheTags(extractedTags, getRcsPrimaryTagExtractionText(formData));
|
|
601
597
|
}
|
|
602
|
-
if (channel === CHANNELS.WHATSAPP) {
|
|
603
|
-
return mergeSyntheticMustacheTags(extractedTags, getWhatsappTagExtractionText(formData));
|
|
604
|
-
}
|
|
605
|
-
if (channel === CHANNELS.ZALO) {
|
|
606
|
-
return mergeZaloUnfilledParamTags(extractedTags, formData);
|
|
607
|
-
}
|
|
608
598
|
return extractedTags ?? [];
|
|
609
599
|
}, [
|
|
610
600
|
channel,
|
|
@@ -690,8 +680,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
690
680
|
const tagName = tagPath.split('.').pop(); // Get the actual tag name from the path
|
|
691
681
|
// Escape regex metacharacters in tagName (arbitrary JSON key) so it can't break the pattern.
|
|
692
682
|
const escapedTagName = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
693
|
-
|
|
694
|
-
const tagRegex = new RegExp(`\\{\\{${escapedTagName}\\}\\}`, 'g');
|
|
683
|
+
const tagRegex = new RegExp(`{{${escapedTagName}}}`, 'g');
|
|
695
684
|
const rawValue = tagValues[tagPath];
|
|
696
685
|
// Preserve legitimate falsy values (0, false); only an unset/blank slot keeps the placeholder.
|
|
697
686
|
const hasValue = rawValue !== null && rawValue !== undefined && rawValue !== '';
|
|
@@ -958,6 +947,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
958
947
|
return {
|
|
959
948
|
...basePayload,
|
|
960
949
|
messageTitle: resolveTagsInText(formDataObj['template-subject'], customValuesObj),
|
|
950
|
+
messageSubtitle: resolveTagsInText(formDataObj['template-preheader'], customValuesObj),
|
|
961
951
|
messageBody: contentStr,
|
|
962
952
|
};
|
|
963
953
|
|
|
@@ -1080,6 +1070,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
1080
1070
|
case CHANNELS.EMAIL:
|
|
1081
1071
|
return {
|
|
1082
1072
|
templateSubject: formDataObj['template-subject'] || '',
|
|
1073
|
+
templatePreheader: formDataObj['template-preheader'] || '',
|
|
1083
1074
|
templateContent: contentStr,
|
|
1084
1075
|
};
|
|
1085
1076
|
|
|
@@ -1421,13 +1412,22 @@ const CommonTestAndPreview = (props) => {
|
|
|
1421
1412
|
|| formDataObj?.base?.['template-subject']
|
|
1422
1413
|
|| '';
|
|
1423
1414
|
|
|
1415
|
+
// Extract pre-header from formData (same shapes as subject)
|
|
1416
|
+
const preheader = formDataObj?.['template-preheader']
|
|
1417
|
+
|| formDataObj?.[0]?.['template-preheader']
|
|
1418
|
+
|| formDataObj?.base?.['template-preheader']
|
|
1419
|
+
|| '';
|
|
1420
|
+
|
|
1424
1421
|
// Extract body from previewData (resolved) or content
|
|
1425
1422
|
const messageBody = previewDataObj?.resolvedBody || contentStr || '';
|
|
1426
1423
|
|
|
1427
|
-
// Resolve tags in subject if custom values are provided
|
|
1424
|
+
// Resolve tags in subject/pre-header if custom values are provided
|
|
1428
1425
|
const resolvedSubject = customValuesObj && Object.keys(customValuesObj).length > 0
|
|
1429
1426
|
? resolveTagsInText(subject, customValuesObj)
|
|
1430
1427
|
: subject;
|
|
1428
|
+
const resolvedPreheader = customValuesObj && Object.keys(customValuesObj).length > 0
|
|
1429
|
+
? resolveTagsInText(preheader, customValuesObj)
|
|
1430
|
+
: preheader;
|
|
1431
1431
|
|
|
1432
1432
|
const emailChannelSettings = {
|
|
1433
1433
|
channel: CHANNELS.EMAIL,
|
|
@@ -1453,6 +1453,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
1453
1453
|
channel: CHANNELS.EMAIL,
|
|
1454
1454
|
messageBody,
|
|
1455
1455
|
messageSubject: resolvedSubject,
|
|
1456
|
+
messagePreHeader: resolvedPreheader,
|
|
1456
1457
|
},
|
|
1457
1458
|
};
|
|
1458
1459
|
}
|
|
@@ -1557,7 +1558,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
1557
1558
|
|
|
1558
1559
|
// Build whatsappMedia structure
|
|
1559
1560
|
const whatsappMedia = {
|
|
1560
|
-
header:
|
|
1561
|
+
header: formDataObj?.templateHeader || formDataObj?.header || '',
|
|
1561
1562
|
footer: formDataObj?.templateFooter || formDataObj?.footer || '',
|
|
1562
1563
|
headerVarMapped: formDataObj?.headerVarMapped || formDataObj?.headerVarMappedData || {},
|
|
1563
1564
|
headerTemplate: formDataObj?.headerTemplate || formDataObj?.templateHeader || '',
|
|
@@ -2425,10 +2426,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2425
2426
|
const resolvedVarMapped = {};
|
|
2426
2427
|
Object.keys(templateConfigs.varMapped).forEach((key) => {
|
|
2427
2428
|
const value = templateConfigs.varMapped[key];
|
|
2428
|
-
|
|
2429
|
-
resolvedVarMapped[key] = value
|
|
2430
|
-
? resolveTagsInText(value, customValuesObj)
|
|
2431
|
-
: (customValuesObj[key] ?? '');
|
|
2429
|
+
resolvedVarMapped[key] = resolveTagsInText(value, customValuesObj);
|
|
2432
2430
|
});
|
|
2433
2431
|
templateConfigs.varMapped = resolvedVarMapped;
|
|
2434
2432
|
}
|
|
@@ -2459,9 +2457,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2459
2457
|
const resolvedVarMapped = {};
|
|
2460
2458
|
Object.keys(varMapped).forEach((key) => {
|
|
2461
2459
|
const value = varMapped[key];
|
|
2462
|
-
resolvedVarMapped[key] = value
|
|
2463
|
-
? resolveTagsInText(value, customValuesObj)
|
|
2464
|
-
: (customValuesObj[key] ?? '');
|
|
2460
|
+
resolvedVarMapped[key] = resolveTagsInText(value, customValuesObj);
|
|
2465
2461
|
});
|
|
2466
2462
|
varMapped = resolvedVarMapped;
|
|
2467
2463
|
}
|
|
@@ -2551,27 +2547,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
2551
2547
|
*
|
|
2552
2548
|
* IMPORTANT: Use raw content/formData initially, only use previewDataHtml if preview call was made
|
|
2553
2549
|
*/
|
|
2554
|
-
// Fills last-applied test values into raw template text when the preview API reply isn't usable.
|
|
2555
|
-
const resolvePreviewText = (text) => (
|
|
2556
|
-
typeof text === 'string' && previewTagValues ? resolveTagsInText(text, previewTagValues) : text
|
|
2557
|
-
);
|
|
2558
|
-
|
|
2559
2550
|
// Memoized RCS carousel content: only recomputes when the API response or raw template changes,
|
|
2560
2551
|
// NOT on every customValues keystroke. Uses previewCustomValuesRef captured at Update Preview time.
|
|
2561
2552
|
const rcsCarouselContentObj = useMemo(() => {
|
|
2562
2553
|
if (channel !== CHANNELS.RCS) return null;
|
|
2563
|
-
// Standalone card: fill entered values client-side; the preview API leaves numbered {{N}} unresolved.
|
|
2564
|
-
const fillStandaloneCard = (card) => (
|
|
2565
|
-
hasPreviewCallBeenMade && previewTagValues && card && typeof card === 'object' && !Array.isArray(card.carouselData)
|
|
2566
|
-
? {
|
|
2567
|
-
...card,
|
|
2568
|
-
templateHeader: resolvePreviewText(card.templateHeader),
|
|
2569
|
-
templateMessage: resolvePreviewText(card.templateMessage),
|
|
2570
|
-
rcsTitle: resolvePreviewText(card.rcsTitle),
|
|
2571
|
-
rcsDesc: resolvePreviewText(card.rcsDesc),
|
|
2572
|
-
}
|
|
2573
|
-
: card
|
|
2574
|
-
);
|
|
2575
2554
|
let resolvedContent = null;
|
|
2576
2555
|
if (hasPreviewCallBeenMade && previewDataHtml?.resolvedBody) {
|
|
2577
2556
|
resolvedContent = previewDataHtml.resolvedBody;
|
|
@@ -2579,7 +2558,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2579
2558
|
try { resolvedContent = JSON.parse(resolvedContent); } catch (e) { resolvedContent = null; }
|
|
2580
2559
|
}
|
|
2581
2560
|
}
|
|
2582
|
-
if (resolvedContent && typeof resolvedContent === 'object') return
|
|
2561
|
+
if (resolvedContent && typeof resolvedContent === 'object') return resolvedContent;
|
|
2583
2562
|
|
|
2584
2563
|
let rawRcs = content;
|
|
2585
2564
|
if (typeof rawRcs === 'string') {
|
|
@@ -2604,9 +2583,9 @@ const CommonTestAndPreview = (props) => {
|
|
|
2604
2583
|
}),
|
|
2605
2584
|
};
|
|
2606
2585
|
}
|
|
2607
|
-
return
|
|
2586
|
+
return rawRcs || {};
|
|
2608
2587
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2609
|
-
}, [channel, content, hasPreviewCallBeenMade, previewDataHtml, formData
|
|
2588
|
+
}, [channel, content, hasPreviewCallBeenMade, previewDataHtml, formData]);
|
|
2610
2589
|
|
|
2611
2590
|
const prepareUnifiedPreviewProps = () => {
|
|
2612
2591
|
// Prepare content based on channel
|
|
@@ -2620,6 +2599,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
2620
2599
|
resolvedTitle: hasPreviewCallBeenMade && previewDataHtml?.resolvedTitle
|
|
2621
2600
|
? previewDataHtml.resolvedTitle
|
|
2622
2601
|
: formData?.['template-subject'] || '',
|
|
2602
|
+
// The liquid preview API returns the resolved pre-header as `resolvedSubtitle`.
|
|
2603
|
+
resolvedPreheader: hasPreviewCallBeenMade && previewDataHtml?.resolvedSubtitle
|
|
2604
|
+
? previewDataHtml.resolvedSubtitle
|
|
2605
|
+
: formData?.['template-preheader'] || '',
|
|
2623
2606
|
};
|
|
2624
2607
|
} else if (channel === CHANNELS.SMS) {
|
|
2625
2608
|
// For SMS, content is the text message (string)
|
|
@@ -2668,14 +2651,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
2668
2651
|
// Use raw content if no preview call was made or resolvedContent is not available
|
|
2669
2652
|
contentObj = parsedContent || {};
|
|
2670
2653
|
}
|
|
2671
|
-
// WhatsApp: fill entered values client-side; the preview API leaves numbered {{N}} unresolved.
|
|
2672
|
-
if (channel === CHANNELS.WHATSAPP && hasPreviewCallBeenMade) {
|
|
2673
|
-
contentObj = {
|
|
2674
|
-
...contentObj,
|
|
2675
|
-
templateHeader: resolvePreviewText(contentObj.templateHeader),
|
|
2676
|
-
templateMsg: resolvePreviewText(contentObj.templateMsg),
|
|
2677
|
-
};
|
|
2678
|
-
}
|
|
2679
2654
|
} else if (channel === CHANNELS.RCS) {
|
|
2680
2655
|
// rcsCarouselContentObj is a useMemo that handles both the API-response path and the
|
|
2681
2656
|
// client-side fallback. It is stable across keystrokes (only recomputes when
|
|
@@ -2972,12 +2947,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
2972
2947
|
});
|
|
2973
2948
|
|
|
2974
2949
|
// Always extract tags when content changes
|
|
2975
|
-
const { templateSubject, templateContent } = prepareTagExtractionPayload(
|
|
2950
|
+
const { templateSubject, templatePreheader, templateContent } = prepareTagExtractionPayload(
|
|
2976
2951
|
channel,
|
|
2977
2952
|
formData,
|
|
2978
2953
|
htmlFile,
|
|
2979
2954
|
);
|
|
2980
|
-
actions.extractTagsRequested({ templateSubject, templateContent });
|
|
2955
|
+
actions.extractTagsRequested({ templateSubject, templatePreheader, templateContent });
|
|
2981
2956
|
}
|
|
2982
2957
|
|
|
2983
2958
|
// Restore original handler
|
|
@@ -2992,32 +2967,32 @@ const CommonTestAndPreview = (props) => {
|
|
|
2992
2967
|
const templateContent = currentTabData?.[activeTab]?.['template-content'];
|
|
2993
2968
|
if (templateContent) {
|
|
2994
2969
|
// Extract tags when showing (but don't set previewDataHtml - use raw content initially)
|
|
2995
|
-
const { templateSubject, templateContent: extractContent } = prepareTagExtractionPayload(
|
|
2970
|
+
const { templateSubject, templatePreheader, templateContent: extractContent } = prepareTagExtractionPayload(
|
|
2996
2971
|
channel,
|
|
2997
2972
|
formData,
|
|
2998
2973
|
templateContent
|
|
2999
2974
|
);
|
|
3000
|
-
actions.extractTagsRequested({ templateSubject, templateContent: extractContent });
|
|
2975
|
+
actions.extractTagsRequested({ templateSubject, templatePreheader, templateContent: extractContent });
|
|
3001
2976
|
} else {
|
|
3002
2977
|
// Fallback to content prop if no template content
|
|
3003
|
-
const { templateSubject, templateContent: extractContent } = prepareTagExtractionPayload(
|
|
2978
|
+
const { templateSubject, templatePreheader, templateContent: extractContent } = prepareTagExtractionPayload(
|
|
3004
2979
|
channel,
|
|
3005
2980
|
formData,
|
|
3006
2981
|
getCurrentContent
|
|
3007
2982
|
);
|
|
3008
|
-
actions.extractTagsRequested({ templateSubject, templateContent: extractContent });
|
|
2983
|
+
actions.extractTagsRequested({ templateSubject, templatePreheader, templateContent: extractContent });
|
|
3009
2984
|
}
|
|
3010
2985
|
}
|
|
3011
2986
|
} else {
|
|
3012
2987
|
// For non-email channels, extract tags from content
|
|
3013
2988
|
// Use getCurrentContent for SMS (extracts from formData) or content prop for others
|
|
3014
2989
|
const contentToConvert = getCurrentContent;
|
|
3015
|
-
const { templateSubject, templateContent } = prepareTagExtractionPayload(
|
|
2990
|
+
const { templateSubject, templatePreheader, templateContent } = prepareTagExtractionPayload(
|
|
3016
2991
|
channel,
|
|
3017
2992
|
formData || {},
|
|
3018
2993
|
contentToConvert
|
|
3019
2994
|
);
|
|
3020
|
-
actions.extractTagsRequested({ templateSubject, templateContent });
|
|
2995
|
+
actions.extractTagsRequested({ templateSubject, templatePreheader, templateContent });
|
|
3021
2996
|
}
|
|
3022
2997
|
|
|
3023
2998
|
// Fetch test customers and groups if enabled
|
|
@@ -3098,12 +3073,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
3098
3073
|
const handleContentUpdate = (contentToUpdate) => {
|
|
3099
3074
|
// Extract tags from content (but don't set previewDataHtml - use raw content initially)
|
|
3100
3075
|
// previewDataHtml will only be set when preview API call is made
|
|
3101
|
-
const { templateSubject, templateContent: extractContent } = prepareTagExtractionPayload(
|
|
3076
|
+
const { templateSubject, templatePreheader, templateContent: extractContent } = prepareTagExtractionPayload(
|
|
3102
3077
|
channel,
|
|
3103
3078
|
formData,
|
|
3104
3079
|
contentToUpdate
|
|
3105
3080
|
);
|
|
3106
|
-
actions.extractTagsRequested({ templateSubject, templateContent: extractContent });
|
|
3081
|
+
actions.extractTagsRequested({ templateSubject, templatePreheader, templateContent: extractContent });
|
|
3107
3082
|
};
|
|
3108
3083
|
|
|
3109
3084
|
if (isDragDrop) {
|
|
@@ -3129,7 +3104,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
3129
3104
|
previousBeeContentRef.current = '';
|
|
3130
3105
|
setBeeContent('');
|
|
3131
3106
|
setPreviewDataHtml('');
|
|
3132
|
-
setPreviewTagValues(null);
|
|
3133
3107
|
setSelectedCustomer(null);
|
|
3134
3108
|
setRequiredTags([]);
|
|
3135
3109
|
setOptionalTags([]);
|
|
@@ -3219,7 +3193,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
3219
3193
|
updatedValues,
|
|
3220
3194
|
selectedCustomer
|
|
3221
3195
|
);
|
|
3222
|
-
setPreviewTagValues(updatedValues);
|
|
3223
3196
|
actions.updatePreviewRequested(payload);
|
|
3224
3197
|
setHasPreviewCallBeenMade(true); // Mark that preview call was made
|
|
3225
3198
|
void syncSmsFallbackPreview(updatedValues, selectedCustomer);
|
|
@@ -3289,7 +3262,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
3289
3262
|
setTagsExtracted(false);
|
|
3290
3263
|
setPreviewDevice(DESKTOP);
|
|
3291
3264
|
setPreviewDataHtml('');
|
|
3292
|
-
setPreviewTagValues(null);
|
|
3293
3265
|
setHasPreviewCallBeenMade(false); // Reset preview call flag
|
|
3294
3266
|
setViberPreviewTagValues(null);
|
|
3295
3267
|
setSelectedTestEntities([]);
|
|
@@ -3328,7 +3300,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
3328
3300
|
*/
|
|
3329
3301
|
const handleClearSelection = () => {
|
|
3330
3302
|
setSelectedCustomer(null);
|
|
3331
|
-
setPreviewTagValues(null);
|
|
3332
3303
|
setHasPreviewCallBeenMade(false); // Reset flag when customer is cleared
|
|
3333
3304
|
setViberPreviewTagValues(null);
|
|
3334
3305
|
|
|
@@ -3392,8 +3363,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
3392
3363
|
if (channel === CHANNELS.VIBER) {
|
|
3393
3364
|
setViberPreviewTagValues({ ...emptyValues });
|
|
3394
3365
|
}
|
|
3395
|
-
previewCustomValuesRef.current = emptyValues;
|
|
3396
|
-
setPreviewTagValues(emptyValues);
|
|
3397
3366
|
actions.updatePreviewRequested(payload);
|
|
3398
3367
|
setHasPreviewCallBeenMade(true); // Mark that preview call was made
|
|
3399
3368
|
};
|
|
@@ -3407,7 +3376,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
3407
3376
|
// Capture customValues at click time so the carousel preview only updates here,
|
|
3408
3377
|
// not on every subsequent keystroke.
|
|
3409
3378
|
previewCustomValuesRef.current = customValues;
|
|
3410
|
-
setPreviewTagValues(customValues);
|
|
3411
3379
|
// For RCS: always dispatch with RCS channel/content so previewDataHtml stays RCS-shaped,
|
|
3412
3380
|
// even when the user triggers update from the SMS fallback tab.
|
|
3413
3381
|
// SMS fallback preview is kept in sync by syncSmsFallbackPreview via smsFallbackPreviewText.
|
|
@@ -3507,13 +3475,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
3507
3475
|
if (channel === CHANNELS.SMS && !smsTemplateHasMustacheTags(smsEditorBody)) {
|
|
3508
3476
|
smsTagSource = [];
|
|
3509
3477
|
}
|
|
3510
|
-
// Unmapped variables become fillable test values, like RCS.
|
|
3511
|
-
if (channel === CHANNELS.WHATSAPP) {
|
|
3512
|
-
smsTagSource = mergeSyntheticMustacheTags(smsTagSource, getWhatsappTagExtractionText(formData));
|
|
3513
|
-
}
|
|
3514
|
-
if (channel === CHANNELS.ZALO) {
|
|
3515
|
-
smsTagSource = mergeZaloUnfilledParamTags(smsTagSource, formData);
|
|
3516
|
-
}
|
|
3517
3478
|
const { required, optional, hasPersonalizationTags } = categorizeTags(smsTagSource);
|
|
3518
3479
|
setRequiredTags(required);
|
|
3519
3480
|
setOptionalTags(optional);
|
|
@@ -3559,12 +3520,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
3559
3520
|
}
|
|
3560
3521
|
|
|
3561
3522
|
setTagsExtracted(true);
|
|
3562
|
-
const { templateSubject, templateContent } = prepareTagExtractionPayload(
|
|
3523
|
+
const { templateSubject, templatePreheader, templateContent } = prepareTagExtractionPayload(
|
|
3563
3524
|
activeChannelForActions,
|
|
3564
3525
|
formData || {},
|
|
3565
3526
|
contentToExtract
|
|
3566
3527
|
);
|
|
3567
|
-
actions.extractTagsRequested({ templateSubject, templateContent });
|
|
3528
|
+
actions.extractTagsRequested({ templateSubject, templatePreheader, templateContent });
|
|
3568
3529
|
};
|
|
3569
3530
|
|
|
3570
3531
|
/**
|
|
@@ -70,10 +70,11 @@ export function* searchCustomersSaga(action) {
|
|
|
70
70
|
// Extract Tags Saga
|
|
71
71
|
export function* extractTagsSaga(action) {
|
|
72
72
|
try {
|
|
73
|
-
const { templateSubject, templateContent } = action.payload;
|
|
73
|
+
const { templateSubject, templatePreheader, templateContent } = action.payload;
|
|
74
74
|
|
|
75
75
|
const payload = {
|
|
76
76
|
messageTitle: templateSubject || '',
|
|
77
|
+
messageSubtitle: templatePreheader || '',
|
|
77
78
|
messageBody: templateContent || '',
|
|
78
79
|
};
|
|
79
80
|
|