@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.
Files changed (78) hide show
  1. package/containers/App/constants.js +1 -0
  2. package/index.html +3 -1
  3. package/package.json +1 -1
  4. package/services/api.js +4 -4
  5. package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -3
  6. package/tests/integration/TemplateCreation/api-response.js +5 -0
  7. package/tests/integration/TemplateCreation/msw-handler.js +42 -63
  8. package/utils/common.js +7 -0
  9. package/utils/commonUtils.js +2 -6
  10. package/utils/createPayload.js +272 -0
  11. package/utils/tests/createPayload.test.js +761 -0
  12. package/v2Components/CapImageUpload/index.js +59 -46
  13. package/v2Components/CapInAppCTA/index.js +1 -0
  14. package/v2Components/CapMpushCTA/constants.js +25 -0
  15. package/v2Components/CapMpushCTA/index.js +332 -0
  16. package/v2Components/CapMpushCTA/index.scss +95 -0
  17. package/v2Components/CapMpushCTA/messages.js +89 -0
  18. package/v2Components/CapTagList/index.js +177 -120
  19. package/v2Components/CapVideoUpload/constants.js +3 -0
  20. package/v2Components/CapVideoUpload/index.js +167 -110
  21. package/v2Components/CapVideoUpload/messages.js +16 -0
  22. package/v2Components/Carousel/index.js +15 -13
  23. package/v2Components/CustomerSearchSection/index.js +12 -7
  24. package/v2Components/ErrorInfoNote/style.scss +1 -0
  25. package/v2Components/MobilePushPreviewV2/index.js +37 -5
  26. package/v2Components/TemplatePreview/_templatePreview.scss +114 -72
  27. package/v2Components/TemplatePreview/assets/images/Android _ With date and time.svg +29 -0
  28. package/v2Components/TemplatePreview/assets/images/android.svg +9 -0
  29. package/v2Components/TemplatePreview/assets/images/iOS _ With date and time.svg +26 -0
  30. package/v2Components/TemplatePreview/assets/images/ios.svg +9 -0
  31. package/v2Components/TemplatePreview/index.js +178 -50
  32. package/v2Components/TemplatePreview/messages.js +4 -0
  33. package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +169 -0
  34. package/v2Components/TestAndPreviewSlidebox/LeftPanelContent.js +95 -0
  35. package/v2Components/TestAndPreviewSlidebox/PreviewSection.js +69 -0
  36. package/v2Components/TestAndPreviewSlidebox/SendTestMessage.js +68 -0
  37. package/v2Components/TestAndPreviewSlidebox/index.js +67 -246
  38. package/v2Components/TestAndPreviewSlidebox/tests/CustomValuesEditor.test.js +425 -0
  39. package/v2Components/TestAndPreviewSlidebox/tests/LeftPanelContent.test.js +400 -0
  40. package/v2Components/TestAndPreviewSlidebox/tests/SendTestMessage.test.js +448 -0
  41. package/v2Containers/CreativesContainer/SlideBoxContent.js +9 -9
  42. package/v2Containers/CreativesContainer/index.js +193 -134
  43. package/v2Containers/Email/index.js +15 -2
  44. package/v2Containers/InApp/constants.js +1 -0
  45. package/v2Containers/InApp/index.js +13 -13
  46. package/v2Containers/MobilePush/Create/index.js +1 -0
  47. package/v2Containers/MobilePush/commonMethods.js +7 -14
  48. package/v2Containers/MobilePushNew/actions.js +116 -0
  49. package/v2Containers/MobilePushNew/components/CtaButtons.js +170 -0
  50. package/v2Containers/MobilePushNew/components/MediaUploaders.js +754 -0
  51. package/v2Containers/MobilePushNew/components/PlatformContentFields.js +279 -0
  52. package/v2Containers/MobilePushNew/components/index.js +5 -0
  53. package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +779 -0
  54. package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +2114 -0
  55. package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +343 -0
  56. package/v2Containers/MobilePushNew/constants.js +115 -0
  57. package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +1299 -0
  58. package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1223 -0
  59. package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +246 -0
  60. package/v2Containers/MobilePushNew/hooks/useUpload.js +726 -0
  61. package/v2Containers/MobilePushNew/index.js +2280 -0
  62. package/v2Containers/MobilePushNew/index.scss +308 -0
  63. package/v2Containers/MobilePushNew/messages.js +226 -0
  64. package/v2Containers/MobilePushNew/reducer.js +160 -0
  65. package/v2Containers/MobilePushNew/sagas.js +198 -0
  66. package/v2Containers/MobilePushNew/selectors.js +55 -0
  67. package/v2Containers/MobilePushNew/tests/reducer.test.js +741 -0
  68. package/v2Containers/MobilePushNew/tests/sagas.test.js +863 -0
  69. package/v2Containers/MobilePushNew/tests/selectors.test.js +425 -0
  70. package/v2Containers/MobilePushNew/tests/utils.test.js +322 -0
  71. package/v2Containers/MobilePushNew/utils.js +33 -0
  72. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +5 -5
  73. package/v2Containers/TagList/index.js +56 -10
  74. package/v2Containers/Templates/_templates.scss +101 -1
  75. package/v2Containers/Templates/index.js +147 -35
  76. package/v2Containers/Templates/messages.js +8 -0
  77. package/v2Containers/Templates/sagas.js +2 -0
  78. 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/androidPushMessage.svg');
70
- const iPhonePushMessagePhone = require('./assets/images/iPhonePushMessage.svg');
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 getPreviewImage = () => {
414
- if (this.props.device === ANDROID) {
415
- switch (templateLayoutType) {
416
- case INAPP_MESSAGE_LAYOUT_TYPES.MODAL:
417
- return inAppMobileAndroidModal;
418
- case INAPP_MESSAGE_LAYOUT_TYPES.TOPBANNER:
419
- return inAppMobileAndroidTop;
420
- case INAPP_MESSAGE_LAYOUT_TYPES.BOTTOMBANNER:
421
- return inAppMobileAndroidBottom;
422
- default:
423
- return inAppMobileAndroidFull;
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.toLowerCase() === "mobilepush" && (
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-name">
631
- <span className="app-icon"></span>{" "}
632
- {content.appName ? content.appName : "App name"}
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
- <CapImage src={content.bodyImage} alt="" />
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-name">
664
- <span className="app-icon"></span>
665
- {content.appName ? content.appName : "App name"}{" "}
666
- <i className="material-icons">
667
- {this.props.intl.formatMessage(messages.close)}
668
- </i>
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.bodyImage && (
755
+ <div className="body-text">{content.bodyText}</div>
756
+ {(content.bodyImage || content?.bodyVideo?.videoSrc || content.bodyGif) && (
671
757
  <div className="body-image">
672
- <CapImage src={content.bodyImage} alt="" />
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
- <div className="title">{content.header}</div>
676
- <div className="body-text">{content.bodyText}</div>
677
- {content.actions.filter((action) => action.label).length
678
- ? iosActions
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;