@capillarytech/creatives-library 8.0.126 → 8.0.127-alpha.0
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/containers/App/constants.js +1 -0
- package/index.html +3 -1
- package/package.json +1 -1
- package/services/api.js +4 -4
- package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -3
- package/tests/integration/TemplateCreation/api-response.js +5 -0
- package/tests/integration/TemplateCreation/msw-handler.js +42 -63
- package/utils/common.js +7 -0
- package/utils/commonUtils.js +2 -6
- package/utils/createPayload.js +272 -0
- package/utils/tests/createPayload.test.js +761 -0
- package/v2Components/CapImageUpload/index.js +59 -46
- package/v2Components/CapInAppCTA/index.js +1 -0
- package/v2Components/CapMpushCTA/constants.js +25 -0
- package/v2Components/CapMpushCTA/index.js +332 -0
- package/v2Components/CapMpushCTA/index.scss +95 -0
- package/v2Components/CapMpushCTA/messages.js +89 -0
- package/v2Components/CapTagList/index.js +177 -120
- package/v2Components/CapVideoUpload/constants.js +3 -0
- package/v2Components/CapVideoUpload/index.js +167 -110
- package/v2Components/CapVideoUpload/messages.js +16 -0
- package/v2Components/Carousel/index.js +15 -13
- package/v2Components/CustomerSearchSection/index.js +12 -7
- package/v2Components/ErrorInfoNote/style.scss +1 -0
- package/v2Components/MobilePushPreviewV2/index.js +37 -5
- package/v2Components/TemplatePreview/_templatePreview.scss +114 -72
- package/v2Components/TemplatePreview/assets/images/Android _ With date and time.svg +29 -0
- package/v2Components/TemplatePreview/assets/images/android.svg +9 -0
- package/v2Components/TemplatePreview/assets/images/iOS _ With date and time.svg +26 -0
- package/v2Components/TemplatePreview/assets/images/ios.svg +9 -0
- package/v2Components/TemplatePreview/index.js +178 -50
- package/v2Components/TemplatePreview/messages.js +4 -0
- package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +169 -0
- package/v2Components/TestAndPreviewSlidebox/LeftPanelContent.js +95 -0
- package/v2Components/TestAndPreviewSlidebox/PreviewSection.js +69 -0
- package/v2Components/TestAndPreviewSlidebox/SendTestMessage.js +68 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +67 -246
- package/v2Components/TestAndPreviewSlidebox/tests/CustomValuesEditor.test.js +425 -0
- package/v2Components/TestAndPreviewSlidebox/tests/LeftPanelContent.test.js +400 -0
- package/v2Components/TestAndPreviewSlidebox/tests/SendTestMessage.test.js +448 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +9 -9
- package/v2Containers/CreativesContainer/index.js +193 -134
- package/v2Containers/Email/index.js +15 -2
- package/v2Containers/InApp/constants.js +1 -0
- package/v2Containers/InApp/index.js +13 -13
- package/v2Containers/MobilePush/Create/index.js +1 -0
- package/v2Containers/MobilePush/commonMethods.js +7 -14
- package/v2Containers/MobilePushNew/actions.js +116 -0
- package/v2Containers/MobilePushNew/components/CtaButtons.js +170 -0
- package/v2Containers/MobilePushNew/components/MediaUploaders.js +754 -0
- package/v2Containers/MobilePushNew/components/PlatformContentFields.js +279 -0
- package/v2Containers/MobilePushNew/components/index.js +5 -0
- package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +779 -0
- package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +2114 -0
- package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +343 -0
- package/v2Containers/MobilePushNew/constants.js +115 -0
- package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +1299 -0
- package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1223 -0
- package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +246 -0
- package/v2Containers/MobilePushNew/hooks/useUpload.js +726 -0
- package/v2Containers/MobilePushNew/index.js +2280 -0
- package/v2Containers/MobilePushNew/index.scss +308 -0
- package/v2Containers/MobilePushNew/messages.js +226 -0
- package/v2Containers/MobilePushNew/reducer.js +160 -0
- package/v2Containers/MobilePushNew/sagas.js +198 -0
- package/v2Containers/MobilePushNew/selectors.js +55 -0
- package/v2Containers/MobilePushNew/tests/reducer.test.js +741 -0
- package/v2Containers/MobilePushNew/tests/sagas.test.js +863 -0
- package/v2Containers/MobilePushNew/tests/selectors.test.js +425 -0
- package/v2Containers/MobilePushNew/tests/utils.test.js +322 -0
- package/v2Containers/MobilePushNew/utils.js +33 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +5 -5
- package/v2Containers/TagList/index.js +56 -10
- package/v2Containers/Templates/_templates.scss +101 -1
- package/v2Containers/Templates/index.js +147 -35
- package/v2Containers/Templates/messages.js +8 -0
- package/v2Containers/Templates/sagas.js +2 -0
- package/v2Containers/Whatsapp/constants.js +1 -0
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
RCS,
|
|
33
33
|
ZALO,
|
|
34
34
|
INAPP,
|
|
35
|
+
MOBILE_PUSH
|
|
35
36
|
} from '../../v2Containers/CreativesContainer/constants';
|
|
36
37
|
import Carousel from '../Carousel';
|
|
37
38
|
import { VIBER, FACEBOOK } from '../../v2Containers/App/constants';
|
|
@@ -66,8 +67,8 @@ const viberMobileAndroid = require('./assets/images/viberMobile.svg');
|
|
|
66
67
|
const lineImgPlaceholder = require('../../assets/line-image-placeholder.svg');
|
|
67
68
|
const lineStickerPlaceholder = require('../../assets/smiley-placeholder.svg');
|
|
68
69
|
const lineVideoPlaceholder = require('../../assets/rich-video-placeholder.svg');
|
|
69
|
-
const androidPushMessagePhone = require('./assets/images/
|
|
70
|
-
const iPhonePushMessagePhone = require('./assets/images/
|
|
70
|
+
const androidPushMessagePhone = require('./assets/images/Android _ With date and time.svg');
|
|
71
|
+
const iPhonePushMessagePhone = require('./assets/images/iOS _ With date and time.svg');
|
|
71
72
|
|
|
72
73
|
export class TemplatePreview extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
|
73
74
|
onPreviewContentClicked = (channel) => {
|
|
@@ -167,6 +168,53 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
167
168
|
this.props.onTestContentClicked(params);
|
|
168
169
|
};
|
|
169
170
|
|
|
171
|
+
renderMobilePushCarousel = (content) => {
|
|
172
|
+
if (!content?.carouselData || content.carouselData.length === 0) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<div className="msg-container-carousel mobile-push-carousel-container">
|
|
178
|
+
<div className="scroll-container mobile-push-scroll-container">
|
|
179
|
+
{content.carouselData.map((item, idx) => (
|
|
180
|
+
<div
|
|
181
|
+
key={`carousel-card-${idx}`}
|
|
182
|
+
className="mobile-push-carousel-card message-pop align-left message-pop-carousel"
|
|
183
|
+
>
|
|
184
|
+
{item.mediaType === "image" && item.imageUrl && (
|
|
185
|
+
<CapImage
|
|
186
|
+
src={item.imageUrl}
|
|
187
|
+
alt="carousel-img"
|
|
188
|
+
className="carousel-image"
|
|
189
|
+
/>
|
|
190
|
+
)}
|
|
191
|
+
{item.mediaType === "video" && item.videoPreviewImg && (
|
|
192
|
+
<div className="video-preview">
|
|
193
|
+
<CapImage
|
|
194
|
+
src={item.videoPreviewImg}
|
|
195
|
+
alt="carousel-video-preview"
|
|
196
|
+
className="carousel-video-preview"
|
|
197
|
+
/>
|
|
198
|
+
<div className="icon-position">
|
|
199
|
+
<CapImage className="video-icon" src={videoPlay} />
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
<div className="carousel-content">
|
|
204
|
+
<div className="carousel-title">
|
|
205
|
+
{item.title || item.header || ""}
|
|
206
|
+
</div>
|
|
207
|
+
<div className="carousel-message">
|
|
208
|
+
{item.message || item.bodyText || ""}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
))}
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
};
|
|
217
|
+
|
|
170
218
|
render() {
|
|
171
219
|
const {
|
|
172
220
|
channel,
|
|
@@ -213,7 +261,7 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
213
261
|
const type = this.props.location && this.props.location.query.type ? this.props.location.query.type : '';
|
|
214
262
|
let iosActions = [];
|
|
215
263
|
if ((channel || '').toLowerCase() === "mobilepush") {
|
|
216
|
-
iosActions = _.map(content.actions, (action) => {
|
|
264
|
+
iosActions = _.map(content.actions || [], (action) => {
|
|
217
265
|
if (action.label) {
|
|
218
266
|
return (<div className="actions" key={`action-${action.label}`}>
|
|
219
267
|
<span className="action">{action.label.toUpperCase()}</span>
|
|
@@ -410,31 +458,32 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
410
458
|
handlePreviewInNewTab(templatePreviewUrl);
|
|
411
459
|
};
|
|
412
460
|
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
} else {
|
|
426
|
-
switch (templateLayoutType) {
|
|
427
|
-
case INAPP_MESSAGE_LAYOUT_TYPES.MODAL:
|
|
428
|
-
return inAppMobileIOSModal;
|
|
429
|
-
case INAPP_MESSAGE_LAYOUT_TYPES.TOPBANNER:
|
|
430
|
-
return inAppMobileIOSTop;
|
|
431
|
-
case INAPP_MESSAGE_LAYOUT_TYPES.BOTTOMBANNER:
|
|
432
|
-
return inAppMobileIOSBottom;
|
|
433
|
-
default:
|
|
434
|
-
return inAppMobileIOSFull;
|
|
435
|
-
}
|
|
461
|
+
const getPreviewImage = () => {
|
|
462
|
+
if (this.props.device === ANDROID) {
|
|
463
|
+
switch (templateLayoutType) {
|
|
464
|
+
case INAPP_MESSAGE_LAYOUT_TYPES.MODAL:
|
|
465
|
+
return inAppMobileAndroidModal;
|
|
466
|
+
case INAPP_MESSAGE_LAYOUT_TYPES.TOPBANNER:
|
|
467
|
+
return inAppMobileAndroidTop;
|
|
468
|
+
case INAPP_MESSAGE_LAYOUT_TYPES.BOTTOMBANNER:
|
|
469
|
+
return inAppMobileAndroidBottom;
|
|
470
|
+
default:
|
|
471
|
+
return inAppMobileAndroidFull;
|
|
436
472
|
}
|
|
437
|
-
}
|
|
473
|
+
} else {
|
|
474
|
+
switch (templateLayoutType) {
|
|
475
|
+
case INAPP_MESSAGE_LAYOUT_TYPES.MODAL:
|
|
476
|
+
return inAppMobileIOSModal;
|
|
477
|
+
case INAPP_MESSAGE_LAYOUT_TYPES.TOPBANNER:
|
|
478
|
+
return inAppMobileIOSTop;
|
|
479
|
+
case INAPP_MESSAGE_LAYOUT_TYPES.BOTTOMBANNER:
|
|
480
|
+
return inAppMobileIOSBottom;
|
|
481
|
+
default:
|
|
482
|
+
return inAppMobileIOSFull;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
438
487
|
const whatsappUpdatedAccountName = whatsappAccountName || templateData?.versions?.base?.content?.whatsapp?.accountName || '';
|
|
439
488
|
const whatsappUpdatedLen = whatsappContentLen !== undefined ? whatsappContentLen : content?.charCount;
|
|
440
489
|
return (
|
|
@@ -606,7 +655,7 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
606
655
|
) : (
|
|
607
656
|
""
|
|
608
657
|
)}
|
|
609
|
-
{channel && channel.
|
|
658
|
+
{channel && channel.toUpperCase() === MOBILE_PUSH && (
|
|
610
659
|
<div className="shell-v2 align-center" id="mobilepush-preview">
|
|
611
660
|
<CapImage
|
|
612
661
|
className="preview-image"
|
|
@@ -619,7 +668,7 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
619
668
|
/>
|
|
620
669
|
{(!!content.header ||
|
|
621
670
|
!!content.bodyText ||
|
|
622
|
-
!!content.bodyImage) && (
|
|
671
|
+
!!content.bodyImage || !!content.bodyVideo?.videoSrc || !!content.bodyGif) && (
|
|
623
672
|
<div
|
|
624
673
|
className={`${
|
|
625
674
|
this.props.device === "ios" ? "iphone" : this.props.device
|
|
@@ -627,22 +676,54 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
627
676
|
>
|
|
628
677
|
{this.props.device === "android" ? (
|
|
629
678
|
<div className="message-pop align-left">
|
|
630
|
-
<div className="app-
|
|
631
|
-
<
|
|
632
|
-
|
|
679
|
+
<div className="app-header">
|
|
680
|
+
<div className="app-header-left">
|
|
681
|
+
<span className="app-icon">{""}</span>
|
|
682
|
+
<div className="title">{content.header}</div>
|
|
683
|
+
</div>
|
|
684
|
+
<div className="app-header-right">
|
|
685
|
+
<span>2:29 PM</span>
|
|
686
|
+
<CapIcon type="chevron-down" size="sm" />
|
|
687
|
+
</div>
|
|
633
688
|
</div>
|
|
634
|
-
<div className="title">{content.header}</div>
|
|
635
689
|
<div className="body-text">{content.bodyText}</div>
|
|
636
|
-
{content.bodyImage && (
|
|
690
|
+
{(content.bodyImage || content?.bodyVideo?.videoSrc || content.bodyGif) && (
|
|
637
691
|
<div className="body-image">
|
|
638
|
-
|
|
692
|
+
{(() => {
|
|
693
|
+
if (content?.bodyVideo?.videoSrc && !content.bodyImage && !content.bodyGif) {
|
|
694
|
+
return (
|
|
695
|
+
<video
|
|
696
|
+
key={`android-video-${content.bodyVideo.videoSrc}`}
|
|
697
|
+
controls
|
|
698
|
+
style={{ maxWidth: '100%', maxHeight: '120px' }}
|
|
699
|
+
poster={content?.bodyVideo?.videoPreview || undefined}
|
|
700
|
+
>
|
|
701
|
+
<source src={content.bodyVideo.videoSrc} type="video/mp4" />
|
|
702
|
+
{formatMessage(messages.videoNotSupported)}
|
|
703
|
+
</video>
|
|
704
|
+
);
|
|
705
|
+
} else if (content.bodyGif) {
|
|
706
|
+
return (
|
|
707
|
+
<img
|
|
708
|
+
src={content.bodyGif}
|
|
709
|
+
alt="GIF preview"
|
|
710
|
+
style={{ maxWidth: '100%', maxHeight: '120px' }}
|
|
711
|
+
key={`android-gif-${content.bodyGif}`}
|
|
712
|
+
/>
|
|
713
|
+
);
|
|
714
|
+
} else {
|
|
715
|
+
return (
|
|
716
|
+
<CapImage src={content.bodyImage || content?.bodyVideo?.videoPreview} alt="" />
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
})()}
|
|
639
720
|
</div>
|
|
640
721
|
)}
|
|
641
|
-
{content.actions.filter((action) => action.label)
|
|
722
|
+
{(content.actions || []).filter((action) => action.label)
|
|
642
723
|
.length ? (
|
|
643
724
|
<div className="actions">
|
|
644
725
|
{_.map(
|
|
645
|
-
content.actions,
|
|
726
|
+
content.actions || [],
|
|
646
727
|
(action) =>
|
|
647
728
|
!!action.label && (
|
|
648
729
|
<span
|
|
@@ -657,26 +738,73 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
657
738
|
) : (
|
|
658
739
|
""
|
|
659
740
|
)}
|
|
741
|
+
{channel && channel.toLowerCase() === "mobilepush" && this.renderMobilePushCarousel(content)}
|
|
660
742
|
</div>
|
|
661
743
|
) : (
|
|
662
744
|
<div className="message-pop align-left">
|
|
663
|
-
<div className="app-
|
|
664
|
-
<
|
|
665
|
-
|
|
666
|
-
<
|
|
667
|
-
|
|
668
|
-
|
|
745
|
+
<div className="app-header">
|
|
746
|
+
<div className="app-header-left">
|
|
747
|
+
<span className="app-icon">{""}</span>
|
|
748
|
+
<div className="title">{content.header}</div>
|
|
749
|
+
</div>
|
|
750
|
+
<div className="app-header-right">
|
|
751
|
+
<span>2:29 PM</span>
|
|
752
|
+
<CapIcon type="chevron-down" size="sm" />
|
|
753
|
+
</div>
|
|
669
754
|
</div>
|
|
670
|
-
{content.
|
|
755
|
+
<div className="body-text">{content.bodyText}</div>
|
|
756
|
+
{(content.bodyImage || content?.bodyVideo?.videoSrc || content.bodyGif) && (
|
|
671
757
|
<div className="body-image">
|
|
672
|
-
|
|
758
|
+
{(() => {
|
|
759
|
+
if (content?.bodyVideo?.videoSrc && !content.bodyImage && !content.bodyGif) {
|
|
760
|
+
return (
|
|
761
|
+
<video
|
|
762
|
+
key={`ios-video-${content.bodyVideo.videoSrc}`}
|
|
763
|
+
controls
|
|
764
|
+
style={{ maxWidth: '100%', maxHeight: '120px' }}
|
|
765
|
+
poster={content?.bodyVideo?.videoPreview || undefined}
|
|
766
|
+
>
|
|
767
|
+
<source src={content.bodyVideo.videoSrc} type="video/mp4" />
|
|
768
|
+
{formatMessage(messages.videoNotSupported)}
|
|
769
|
+
</video>
|
|
770
|
+
);
|
|
771
|
+
} else if (content.bodyGif) {
|
|
772
|
+
return (
|
|
773
|
+
<img
|
|
774
|
+
src={content.bodyGif}
|
|
775
|
+
alt="GIF preview"
|
|
776
|
+
style={{ maxWidth: '100%', maxHeight: '120px' }}
|
|
777
|
+
key={`ios-gif-${content.bodyGif}`}
|
|
778
|
+
/>
|
|
779
|
+
);
|
|
780
|
+
} else {
|
|
781
|
+
return (
|
|
782
|
+
<CapImage src={content.bodyImage || content?.bodyVideo?.videoPreview} alt="" />
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
})()}
|
|
673
786
|
</div>
|
|
674
787
|
)}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
788
|
+
{(content.actions || []).filter((action) => action.label)
|
|
789
|
+
.length ? (
|
|
790
|
+
<div className="actions">
|
|
791
|
+
{_.map(
|
|
792
|
+
content.actions || [],
|
|
793
|
+
(action) =>
|
|
794
|
+
!!action.label && (
|
|
795
|
+
<span
|
|
796
|
+
className="action"
|
|
797
|
+
key={`action-${action.label}`}
|
|
798
|
+
>
|
|
799
|
+
{action.label && action.label.toUpperCase()}
|
|
800
|
+
</span>
|
|
801
|
+
)
|
|
802
|
+
)}
|
|
803
|
+
</div>
|
|
804
|
+
) : (
|
|
805
|
+
""
|
|
806
|
+
)}
|
|
807
|
+
{channel && channel.toLowerCase() === "mobilepush" && this.renderMobilePushCarousel(content)}
|
|
680
808
|
</div>
|
|
681
809
|
)}
|
|
682
810
|
</div>
|
|
@@ -90,4 +90,8 @@ export default defineMessages({
|
|
|
90
90
|
id: `creatives.componentsV2.TemplatePreview.addToCart`,
|
|
91
91
|
defaultMessage: 'Add to cart',
|
|
92
92
|
},
|
|
93
|
+
videoNotSupported: {
|
|
94
|
+
id: 'creatives.componentsV2.TemplatePreview.videoNotSupported',
|
|
95
|
+
defaultMessage: 'Your browser does not support the video tag.',
|
|
96
|
+
},
|
|
93
97
|
});
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
5
|
+
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
6
|
+
import CapSwitch from '@capillarytech/cap-ui-library/CapSwitch';
|
|
7
|
+
import CapButton from '@capillarytech/cap-ui-library/CapButton';
|
|
8
|
+
import CapInput from '@capillarytech/cap-ui-library/CapInput';
|
|
9
|
+
import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
|
|
10
|
+
import messages from './messages';
|
|
11
|
+
|
|
12
|
+
const CustomValuesEditor = ({
|
|
13
|
+
isExtractingTags,
|
|
14
|
+
isUpdatePreviewDisabled,
|
|
15
|
+
showJSON,
|
|
16
|
+
setShowJSON,
|
|
17
|
+
customValues,
|
|
18
|
+
handleJSONTextChange,
|
|
19
|
+
extractedTags,
|
|
20
|
+
requiredTags,
|
|
21
|
+
optionalTags,
|
|
22
|
+
handleCustomValueChange,
|
|
23
|
+
handleDiscardCustomValues,
|
|
24
|
+
handleUpdatePreview,
|
|
25
|
+
isUpdatingPreview,
|
|
26
|
+
formatMessage,
|
|
27
|
+
}) => {
|
|
28
|
+
if (isExtractingTags) {
|
|
29
|
+
return (
|
|
30
|
+
<CapRow className="loading-container">
|
|
31
|
+
<CapSpin size="large" />
|
|
32
|
+
<CapRow className="loading-text">
|
|
33
|
+
<FormattedMessage {...messages.extractingTags} />
|
|
34
|
+
</CapRow>
|
|
35
|
+
</CapRow>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<CapRow className="custom-values-editor">
|
|
41
|
+
{isUpdatePreviewDisabled && (
|
|
42
|
+
<CapLabel type="label16" className="values-missing-message">
|
|
43
|
+
<FormattedMessage {...messages.valuesMissing} />
|
|
44
|
+
</CapLabel>
|
|
45
|
+
)}
|
|
46
|
+
<CapRow className="editor-header">
|
|
47
|
+
<CapRow className="json-toggle">
|
|
48
|
+
<span className="toggle-label">
|
|
49
|
+
<FormattedMessage {...messages.showJSON} />
|
|
50
|
+
</span>
|
|
51
|
+
<CapSwitch
|
|
52
|
+
checked={showJSON}
|
|
53
|
+
onChange={setShowJSON}
|
|
54
|
+
size="small"
|
|
55
|
+
/>
|
|
56
|
+
</CapRow>
|
|
57
|
+
</CapRow>
|
|
58
|
+
{showJSON ? (
|
|
59
|
+
<CapRow className="json-editor">
|
|
60
|
+
<CapRow className="json-editor-container">
|
|
61
|
+
<CapRow className="line-numbers">
|
|
62
|
+
{JSON.stringify(customValues, null, 2).split('\n').map((_, index) => (
|
|
63
|
+
<CapRow key={`line-${index + 1}`} className="line-number">
|
|
64
|
+
{index + 1}
|
|
65
|
+
</CapRow>
|
|
66
|
+
))}
|
|
67
|
+
</CapRow>
|
|
68
|
+
<textarea
|
|
69
|
+
className="json-textarea"
|
|
70
|
+
value={JSON.stringify(customValues, null, 2)}
|
|
71
|
+
onChange={(e) => handleJSONTextChange(e.target.value)}
|
|
72
|
+
placeholder={formatMessage(messages.enterValue)}
|
|
73
|
+
rows={Math.max(10, JSON.stringify(customValues, null, 2).split('\n').length)}
|
|
74
|
+
spellCheck={false}
|
|
75
|
+
/>
|
|
76
|
+
</CapRow>
|
|
77
|
+
</CapRow>
|
|
78
|
+
) : (
|
|
79
|
+
<>
|
|
80
|
+
{extractedTags?.length > 0 && (
|
|
81
|
+
<CapRow className="values-table">
|
|
82
|
+
<CapRow className="table-header">
|
|
83
|
+
<CapLabel type="label31" className="header-cell">
|
|
84
|
+
<FormattedMessage {...messages.personalizationTags} />
|
|
85
|
+
</CapLabel>
|
|
86
|
+
<CapLabel type="label31" className="header-cell">
|
|
87
|
+
<FormattedMessage {...messages.customValues} />
|
|
88
|
+
</CapLabel>
|
|
89
|
+
</CapRow>
|
|
90
|
+
{requiredTags.map((tag) => (
|
|
91
|
+
<CapRow key={tag.fullPath} className="value-row">
|
|
92
|
+
<CapRow className="tag-name">
|
|
93
|
+
{tag.fullPath}
|
|
94
|
+
<span className="required-tag-indicator">*</span>
|
|
95
|
+
</CapRow>
|
|
96
|
+
<CapRow className="tag-input">
|
|
97
|
+
<CapInput
|
|
98
|
+
type="text"
|
|
99
|
+
isRequired
|
|
100
|
+
className="tag-input-field"
|
|
101
|
+
value={customValues[tag.fullPath] || ''}
|
|
102
|
+
onChange={(e) => handleCustomValueChange(tag.fullPath, e.target.value)}
|
|
103
|
+
placeholder={formatMessage(messages.enterValue)}
|
|
104
|
+
size="small"
|
|
105
|
+
/>
|
|
106
|
+
</CapRow>
|
|
107
|
+
</CapRow>
|
|
108
|
+
))}
|
|
109
|
+
{optionalTags.map((tag) => (
|
|
110
|
+
<CapRow key={tag.fullPath} className="value-row">
|
|
111
|
+
<CapRow className="tag-name">{tag.fullPath}</CapRow>
|
|
112
|
+
<CapRow className="tag-input">
|
|
113
|
+
<CapInput
|
|
114
|
+
type="text"
|
|
115
|
+
className="tag-input-field"
|
|
116
|
+
value={customValues[tag.fullPath] || ''}
|
|
117
|
+
onChange={(e) => handleCustomValueChange(tag.fullPath, e.target.value)}
|
|
118
|
+
placeholder={formatMessage(messages.enterValue)}
|
|
119
|
+
size="small"
|
|
120
|
+
/>
|
|
121
|
+
</CapRow>
|
|
122
|
+
</CapRow>
|
|
123
|
+
))}
|
|
124
|
+
</CapRow>
|
|
125
|
+
)}
|
|
126
|
+
</>
|
|
127
|
+
)}
|
|
128
|
+
<div className="editor-actions">
|
|
129
|
+
<CapButton
|
|
130
|
+
className="discard-button"
|
|
131
|
+
type="flat"
|
|
132
|
+
size="small"
|
|
133
|
+
onClick={handleDiscardCustomValues}
|
|
134
|
+
icon="close"
|
|
135
|
+
>
|
|
136
|
+
<FormattedMessage {...messages.discardCustomValues} />
|
|
137
|
+
</CapButton>
|
|
138
|
+
<CapButton
|
|
139
|
+
type="primary"
|
|
140
|
+
size="small"
|
|
141
|
+
onClick={handleUpdatePreview}
|
|
142
|
+
loading={isUpdatingPreview}
|
|
143
|
+
disabled={isUpdatePreviewDisabled}
|
|
144
|
+
>
|
|
145
|
+
<FormattedMessage {...messages.updatePreview} />
|
|
146
|
+
</CapButton>
|
|
147
|
+
</div>
|
|
148
|
+
</CapRow>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
CustomValuesEditor.propTypes = {
|
|
153
|
+
isExtractingTags: PropTypes.bool.isRequired,
|
|
154
|
+
isUpdatePreviewDisabled: PropTypes.bool.isRequired,
|
|
155
|
+
showJSON: PropTypes.bool.isRequired,
|
|
156
|
+
setShowJSON: PropTypes.func.isRequired,
|
|
157
|
+
customValues: PropTypes.object.isRequired,
|
|
158
|
+
handleJSONTextChange: PropTypes.func.isRequired,
|
|
159
|
+
extractedTags: PropTypes.array.isRequired,
|
|
160
|
+
requiredTags: PropTypes.array.isRequired,
|
|
161
|
+
optionalTags: PropTypes.array.isRequired,
|
|
162
|
+
handleCustomValueChange: PropTypes.func.isRequired,
|
|
163
|
+
handleDiscardCustomValues: PropTypes.func.isRequired,
|
|
164
|
+
handleUpdatePreview: PropTypes.func.isRequired,
|
|
165
|
+
isUpdatingPreview: PropTypes.bool.isRequired,
|
|
166
|
+
formatMessage: PropTypes.func.isRequired,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export default CustomValuesEditor;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
5
|
+
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
6
|
+
import CapInfoNote from '@capillarytech/cap-ui-library/CapInfoNote';
|
|
7
|
+
import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
|
|
8
|
+
import CapButton from '@capillarytech/cap-ui-library/CapButton';
|
|
9
|
+
import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
|
|
10
|
+
import CustomerSearchSection from '../CustomerSearchSection';
|
|
11
|
+
import messages from './messages';
|
|
12
|
+
|
|
13
|
+
const LeftPanelContent = ({
|
|
14
|
+
isExtractingTags,
|
|
15
|
+
extractedTags,
|
|
16
|
+
selectedCustomer,
|
|
17
|
+
handleCustomerSelect,
|
|
18
|
+
handleSearchCustomer,
|
|
19
|
+
customers,
|
|
20
|
+
isSearchingCustomer,
|
|
21
|
+
handleClearSelection,
|
|
22
|
+
tagsExtracted,
|
|
23
|
+
handleExtractTags,
|
|
24
|
+
renderCustomValuesEditor,
|
|
25
|
+
}) => {
|
|
26
|
+
if (isExtractingTags) {
|
|
27
|
+
return (
|
|
28
|
+
<CapRow className="loading-container">
|
|
29
|
+
<CapSpin size="large" />
|
|
30
|
+
<CapRow className="loading-text">
|
|
31
|
+
<FormattedMessage {...messages.extractingTags} />
|
|
32
|
+
</CapRow>
|
|
33
|
+
</CapRow>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
if (extractedTags?.length > 0) {
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
{/* Customer Search Section */}
|
|
40
|
+
<CapRow className="panel-section customer-section">
|
|
41
|
+
<CapHeader size="label1" title={<FormattedMessage {...messages.customerSearchTitle} />} />
|
|
42
|
+
<CustomerSearchSection
|
|
43
|
+
selectedCustomer={selectedCustomer}
|
|
44
|
+
onCustomerSelect={handleCustomerSelect}
|
|
45
|
+
onSearch={handleSearchCustomer}
|
|
46
|
+
customers={customers}
|
|
47
|
+
isSearchingCustomer={isSearchingCustomer}
|
|
48
|
+
onClearSelection={handleClearSelection}
|
|
49
|
+
/>
|
|
50
|
+
</CapRow>
|
|
51
|
+
{/* Tags Section */}
|
|
52
|
+
{!tagsExtracted && (
|
|
53
|
+
<CapRow className="panel-section">
|
|
54
|
+
<CapButton type="flat" className="extract-tags-button" onClick={handleExtractTags}>
|
|
55
|
+
<CapLabel type="label33">
|
|
56
|
+
<FormattedMessage {...messages.enterCustomValuesForTags} />
|
|
57
|
+
</CapLabel>
|
|
58
|
+
</CapButton>
|
|
59
|
+
</CapRow>
|
|
60
|
+
)}
|
|
61
|
+
{tagsExtracted && renderCustomValuesEditor()}
|
|
62
|
+
</>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return (
|
|
66
|
+
<CapInfoNote
|
|
67
|
+
className="no-tags-extracted-info-note"
|
|
68
|
+
noteText={(
|
|
69
|
+
<CapLabel type="label31">
|
|
70
|
+
<FormattedMessage {...messages.noTagsExtracted} />
|
|
71
|
+
</CapLabel>
|
|
72
|
+
)}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
LeftPanelContent.propTypes = {
|
|
78
|
+
isExtractingTags: PropTypes.bool.isRequired,
|
|
79
|
+
extractedTags: PropTypes.array.isRequired,
|
|
80
|
+
selectedCustomer: PropTypes.object,
|
|
81
|
+
handleCustomerSelect: PropTypes.func.isRequired,
|
|
82
|
+
handleSearchCustomer: PropTypes.func.isRequired,
|
|
83
|
+
customers: PropTypes.array.isRequired,
|
|
84
|
+
isSearchingCustomer: PropTypes.bool.isRequired,
|
|
85
|
+
handleClearSelection: PropTypes.func.isRequired,
|
|
86
|
+
tagsExtracted: PropTypes.bool.isRequired,
|
|
87
|
+
handleExtractTags: PropTypes.func.isRequired,
|
|
88
|
+
renderCustomValuesEditor: PropTypes.func.isRequired,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
LeftPanelContent.defaultProps = {
|
|
92
|
+
selectedCustomer: null,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export default LeftPanelContent;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
4
|
+
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
5
|
+
import messages from './messages';
|
|
6
|
+
|
|
7
|
+
const PreviewSection = ({
|
|
8
|
+
previewDevice,
|
|
9
|
+
setPreviewDevice,
|
|
10
|
+
selectedCustomer,
|
|
11
|
+
formData,
|
|
12
|
+
isUpdatingPreview,
|
|
13
|
+
previewDataHtml,
|
|
14
|
+
content,
|
|
15
|
+
formatMessage,
|
|
16
|
+
PreviewChrome,
|
|
17
|
+
}) => (
|
|
18
|
+
<CapRow className="preview-section panel-section">
|
|
19
|
+
<PreviewChrome
|
|
20
|
+
device={previewDevice}
|
|
21
|
+
onDeviceChange={setPreviewDevice}
|
|
22
|
+
customer={selectedCustomer}
|
|
23
|
+
subject={formData['template-subject']}
|
|
24
|
+
>
|
|
25
|
+
{isUpdatingPreview && (
|
|
26
|
+
<CapRow className="loading-container">
|
|
27
|
+
<CapSpin />
|
|
28
|
+
<CapRow className="loading-text">{formatMessage(messages.updatingPreview)}</CapRow>
|
|
29
|
+
</CapRow>
|
|
30
|
+
)}
|
|
31
|
+
{!isUpdatingPreview && previewDataHtml && (
|
|
32
|
+
<iframe
|
|
33
|
+
srcDoc={previewDataHtml?.resolvedBody}
|
|
34
|
+
title="Email Preview"
|
|
35
|
+
width="100%"
|
|
36
|
+
height="100%"
|
|
37
|
+
frameBorder="0"
|
|
38
|
+
/>
|
|
39
|
+
)}
|
|
40
|
+
{!isUpdatingPreview && !previewDataHtml && (
|
|
41
|
+
<iframe
|
|
42
|
+
srcDoc={content}
|
|
43
|
+
title="Email Preview"
|
|
44
|
+
width="100%"
|
|
45
|
+
height="100%"
|
|
46
|
+
frameBorder="0" />
|
|
47
|
+
)}
|
|
48
|
+
</PreviewChrome>
|
|
49
|
+
</CapRow>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
PreviewSection.propTypes = {
|
|
53
|
+
previewDevice: PropTypes.string.isRequired,
|
|
54
|
+
setPreviewDevice: PropTypes.func.isRequired,
|
|
55
|
+
selectedCustomer: PropTypes.object,
|
|
56
|
+
formData: PropTypes.object.isRequired,
|
|
57
|
+
isUpdatingPreview: PropTypes.bool.isRequired,
|
|
58
|
+
previewDataHtml: PropTypes.object,
|
|
59
|
+
content: PropTypes.string.isRequired,
|
|
60
|
+
formatMessage: PropTypes.func.isRequired,
|
|
61
|
+
PreviewChrome: PropTypes.elementType.isRequired,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
PreviewSection.defaultProps = {
|
|
65
|
+
selectedCustomer: null,
|
|
66
|
+
previewDataHtml: null,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default PreviewSection;
|