@capillarytech/creatives-library 8.0.167 → 8.0.168-alpha.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.
Files changed (30) hide show
  1. package/package.json +1 -1
  2. package/utils/test-utils.js +6 -2
  3. package/v2Components/CapActionButton/index.js +52 -12
  4. package/v2Components/CapActionButton/messages.js +4 -0
  5. package/v2Components/CapActionButton/tests/index.test.js +135 -0
  6. package/v2Components/CapDeviceContent/index.js +5 -0
  7. package/v2Components/CapInAppCTA/index.js +29 -14
  8. package/v2Components/CapInAppCTA/index.scss +0 -2
  9. package/v2Components/CapInAppCTA/messages.js +4 -0
  10. package/v2Components/CapMpushCTA/index.js +54 -38
  11. package/v2Components/CapMpushCTA/index.scss +2 -2
  12. package/v2Components/CapMpushCTA/messages.js +4 -0
  13. package/v2Components/CapTagListWithInput/index.js +169 -0
  14. package/v2Components/CapTagListWithInput/messages.js +10 -0
  15. package/v2Components/FormBuilder/index.js +93 -1
  16. package/v2Components/TestAndPreviewSlidebox/PreviewSection.js +1 -1
  17. package/v2Components/TestAndPreviewSlidebox/index.js +24 -4
  18. package/v2Containers/Email/index.js +64 -3
  19. package/v2Containers/Email/initialSchema.js +7 -21
  20. package/v2Containers/MobilePush/Create/index.js +23 -3
  21. package/v2Containers/MobilePush/commonMethods.js +24 -3
  22. package/v2Containers/MobilePushNew/components/CtaButtons.js +20 -0
  23. package/v2Containers/MobilePushNew/components/MediaUploaders.js +31 -3
  24. package/v2Containers/MobilePushNew/components/PlatformContentFields.js +34 -3
  25. package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +153 -5
  26. package/v2Containers/MobilePushNew/index.js +9 -0
  27. package/v2Containers/MobilePushNew/index.scss +2 -1
  28. package/v2Containers/Rcs/index.js +77 -71
  29. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +15270 -492
  30. package/v2Containers/Viber/index.js +102 -76
@@ -43,6 +43,7 @@ import {
43
43
  } from "../constants";
44
44
  import messages from "../messages";
45
45
  import { validateExternalLink, validateDeepLink } from "../utils";
46
+ import TagList from "../../TagList";
46
47
 
47
48
  // Initial carousel data structure
48
49
  const CAROUSEL_INITIAL_DATA = {
@@ -102,6 +103,11 @@ const MediaUploaders = ({
102
103
  setCarouselActiveTabIndex,
103
104
  carouselLinkErrors = {}, // Carousel link errors from parent
104
105
  updateCarouselLinkError, // Function to update carousel link errors in parent
106
+ location,
107
+ tags,
108
+ injectedTags,
109
+ selectedOfferDetails,
110
+ handleOnTagsContextChange,
105
111
  }) => {
106
112
  // Carousel state management - separate for Android and iOS
107
113
  const [carouselMediaType, setCarouselMediaType] = useState(IMAGE.toLowerCase());
@@ -563,6 +569,14 @@ const MediaUploaders = ({
563
569
 
564
570
  const button = card?.buttons[0]; // We're handling only one button for now
565
571
 
572
+ const onButtonTagSelect = useCallback(
573
+ (value) => {
574
+ const newUrl = `${button?.externalLinkValue}{{${value}}}`;
575
+ handleCarouselExternalLinkChange(cardIndex, newUrl);
576
+ },
577
+ [handleCarouselExternalLinkChange, button?.externalLinkValue]
578
+ );
579
+
566
580
  return (
567
581
  <>
568
582
  <CapDivider />
@@ -633,9 +647,23 @@ const MediaUploaders = ({
633
647
  )}
634
648
  {button?.linkType === EXTERNAL_LINK && (
635
649
  <CapColumn span={14}>
636
- <CapHeading type="h4" className="buttons-heading">
637
- {formatMessage(messages.externalLink)}
638
- </CapHeading>
650
+ <CapRow>
651
+ <CapHeading type="h4" className="buttons-heading">
652
+ {formatMessage(messages.externalLink)}
653
+ </CapHeading>
654
+ <TagList
655
+ moduleFilterEnabled={
656
+ location?.query?.type !== "embedded"
657
+ }
658
+ label={formatMessage(messages.addLabels)}
659
+ onTagSelect={onButtonTagSelect}
660
+ onContextChange={handleOnTagsContextChange}
661
+ location={location}
662
+ tags={tags}
663
+ injectedTags={injectedTags}
664
+ selectedOfferDetails={selectedOfferDetails}
665
+ />
666
+ </CapRow>
639
667
  <CapInput
640
668
  id={`mobile-push-external-link-input-${activeTab}-${cardIndex}`}
641
669
  onChange={(e) => handleCarouselExternalLinkChange(cardIndex, e.target.value)}
@@ -36,6 +36,10 @@ const PlatformContentFields = ({
36
36
  linkProps,
37
37
  sameContent,
38
38
  formatMessage,
39
+ location,
40
+ tags,
41
+ injectedTags,
42
+ selectedOfferDetails,
39
43
  }) => {
40
44
  const { title: titleError, message: messageError } = errors;
41
45
  const {
@@ -145,6 +149,14 @@ const PlatformContentFields = ({
145
149
  [tagListProps, onTagSelect, handleOnTagsContextChange]
146
150
  );
147
151
 
152
+ const onButtonTagSelect = useCallback(
153
+ (value) => {
154
+ const newUrl = `${externalLinkValue}{{${value}}}`;
155
+ handleExternalLinkChange(newUrl);
156
+ },
157
+ [handleExternalLinkChange, externalLinkValue]
158
+ );
159
+
148
160
  return (
149
161
  <>
150
162
  {sameContent && (
@@ -214,6 +226,11 @@ const PlatformContentFields = ({
214
226
  mediaType={content.mediaType}
215
227
  {...mediaUploaderProps}
216
228
  formatMessage={formatMessage}
229
+ location={location}
230
+ tags={tags}
231
+ injectedTags={injectedTags}
232
+ selectedOfferDetails={selectedOfferDetails}
233
+ handleOnTagsContextChange={handleOnTagsContextChange}
217
234
  />
218
235
  </CapRow>
219
236
  {content?.mediaType !== CAROUSEL && (
@@ -281,9 +298,23 @@ const PlatformContentFields = ({
281
298
  )}
282
299
  {content.linkType === EXTERNAL_LINK && (
283
300
  <CapColumn span={14}>
284
- <CapHeading type="h4" className="buttons-heading">
285
- {formatMessage(messages.externalLink)}
286
- </CapHeading>
301
+ <CapRow style={{ display: "flex" }}>
302
+ <CapHeading type="h4" className="buttons-heading">
303
+ {formatMessage(messages.externalLink)}
304
+ </CapHeading>
305
+ <TagList
306
+ moduleFilterEnabled={
307
+ location?.query?.type !== "embedded"
308
+ }
309
+ label={formatMessage(messages.addLabels)}
310
+ onTagSelect={onButtonTagSelect}
311
+ onContextChange={handleOnTagsContextChange}
312
+ location={location}
313
+ tags={tags}
314
+ injectedTags={injectedTags}
315
+ selectedOfferDetails={selectedOfferDetails}
316
+ />
317
+ </CapRow>
287
318
  <CapInput
288
319
  id="mobile-push-external-link-input"
289
320
  onChange={onExternalLinkChange}
@@ -53,6 +53,149 @@ jest.mock('../../../../v2Components/CapImageUpload', () =>
53
53
  }
54
54
  );
55
55
 
56
+ // Mock Cap UI Library components that use styled-components
57
+ jest.mock('@capillarytech/cap-ui-library/CapButton', () =>
58
+ function MockCapButton({ children, ...props }) {
59
+ return <button {...props}>{children}</button>;
60
+ }
61
+ );
62
+
63
+ jest.mock('@capillarytech/cap-ui-library/CapCard', () =>
64
+ function MockCapCard({ children, title, extra, ...props }) {
65
+ return (
66
+ <div {...props} data-testid="cap-card">
67
+ {title && <div data-testid="card-title">{title}</div>}
68
+ {extra && <div data-testid="card-extra">{extra}</div>}
69
+ {children}
70
+ </div>
71
+ );
72
+ }
73
+ );
74
+
75
+ jest.mock('@capillarytech/cap-ui-library/CapIcon', () =>
76
+ function MockCapIcon({ type, ...props }) {
77
+ return <span {...props} data-testid={`cap-icon-${type}`}>Icon</span>;
78
+ }
79
+ );
80
+
81
+ jest.mock('@capillarytech/cap-ui-library/CapRadioGroup', () =>
82
+ function MockCapRadioGroup({ options, value, onChange, ...props }) {
83
+ return (
84
+ <div {...props}>
85
+ {options?.map((option, index) => (
86
+ <label key={index}>
87
+ <input
88
+ type="radio"
89
+ checked={value === option.value}
90
+ onChange={() => onChange(option.value)}
91
+ />
92
+ {option.label}
93
+ </label>
94
+ ))}
95
+ </div>
96
+ );
97
+ }
98
+ );
99
+
100
+ jest.mock('@capillarytech/cap-ui-library/CapSelect', () => ({
101
+ CapCustomSelect: function MockCapCustomSelect({ options, value, onChange, placeholder, ...props }) {
102
+ return (
103
+ <select {...props} value={value} onChange={(e) => onChange(e.target.value)}>
104
+ <option value="">{placeholder}</option>
105
+ {options?.map((option, index) => (
106
+ <option key={index} value={option.value}>
107
+ {option.label}
108
+ </option>
109
+ ))}
110
+ </select>
111
+ );
112
+ }
113
+ }));
114
+
115
+ jest.mock('@capillarytech/cap-ui-library/CapRow', () =>
116
+ function MockCapRow({ children, ...props }) {
117
+ return <div {...props} data-testid="cap-row">{children}</div>;
118
+ }
119
+ );
120
+
121
+ jest.mock('@capillarytech/cap-ui-library/CapColumn', () =>
122
+ function MockCapColumn({ children, span, ...props }) {
123
+ return <div {...props} data-testid="cap-column">{children}</div>;
124
+ }
125
+ );
126
+
127
+ jest.mock('@capillarytech/cap-ui-library/CapHeading', () =>
128
+ function MockCapHeading({ children, type, ...props }) {
129
+ const Tag = type === 'h4' ? 'h4' : 'div';
130
+ return <Tag {...props} data-testid="cap-heading">{children}</Tag>;
131
+ }
132
+ );
133
+
134
+ jest.mock('@capillarytech/cap-ui-library/CapDivider', () =>
135
+ function MockCapDivider({ type, ...props }) {
136
+ return <hr {...props} data-testid="cap-divider" />;
137
+ }
138
+ );
139
+
140
+ jest.mock('@capillarytech/cap-ui-library/CapCheckbox', () =>
141
+ function MockCapCheckbox({ children, checked, onChange, ...props }) {
142
+ return (
143
+ <label>
144
+ <input
145
+ type="checkbox"
146
+ checked={checked}
147
+ onChange={(e) => onChange(e)}
148
+ {...props}
149
+ />
150
+ {children}
151
+ </label>
152
+ );
153
+ }
154
+ );
155
+
156
+ jest.mock('@capillarytech/cap-ui-library/CapLabel', () =>
157
+ function MockCapLabel({ children, ...props }) {
158
+ return <label {...props} data-testid="cap-label">{children}</label>;
159
+ }
160
+ );
161
+
162
+ jest.mock('@capillarytech/cap-ui-library/CapInput', () =>
163
+ function MockCapInput({ onChange, value, placeholder, error, ...props }) {
164
+ return (
165
+ <input
166
+ type="text"
167
+ value={value || ''}
168
+ onChange={onChange}
169
+ placeholder={placeholder}
170
+ {...props}
171
+ data-testid="cap-input"
172
+ />
173
+ );
174
+ }
175
+ );
176
+
177
+ jest.mock('@capillarytech/cap-ui-library/CapError', () =>
178
+ function MockCapError({ children, ...props }) {
179
+ return <div {...props} data-testid="cap-error">{children}</div>;
180
+ }
181
+ );
182
+
183
+ jest.mock('../../../../v2Containers/TagList', () =>
184
+ function MockTagList({ label, onTagSelect, onContextChange, ...props }) {
185
+ return (
186
+ <div data-testid="tag-list" {...props}>
187
+ <button
188
+ type="button"
189
+ onClick={() => onTagSelect && onTagSelect('test-tag')}
190
+ data-testid="tag-select-button"
191
+ >
192
+ {label || 'Add Label'}
193
+ </button>
194
+ </div>
195
+ );
196
+ }
197
+ );
198
+
56
199
  jest.mock('../../../../v2Components/CapVideoUpload', () =>
57
200
  function MockCapVideoUpload(props) {
58
201
  return (
@@ -352,7 +495,7 @@ const mockStore = configureStore({}, initialReducer, history);
352
495
 
353
496
  // Helper function for formatMessage
354
497
  const mockFormatMessage = (message, values = {}) => {
355
- if (typeof message === 'object' && message.defaultMessage) {
498
+ if (message && typeof message === 'object' && message.defaultMessage) {
356
499
  let result = message.defaultMessage;
357
500
  if (values && typeof values === 'object') {
358
501
  Object.keys(values).forEach(key => {
@@ -361,7 +504,7 @@ const mockFormatMessage = (message, values = {}) => {
361
504
  }
362
505
  return result;
363
506
  }
364
- return message;
507
+ return message || '';
365
508
  };
366
509
 
367
510
  const defaultProps = {
@@ -381,7 +524,7 @@ const defaultProps = {
381
524
  setUpdateMpushVideoSrc: jest.fn(),
382
525
  videoDataForVideo: {},
383
526
  videoDataForGif: {},
384
- formatMessage: jest.fn((message) => message.defaultMessage),
527
+ formatMessage: jest.fn(mockFormatMessage),
385
528
  linkProps: {
386
529
  deepLink: [],
387
530
  },
@@ -395,6 +538,11 @@ const defaultProps = {
395
538
  setCarouselActiveTabIndex: jest.fn(),
396
539
  carouselLinkErrors: {},
397
540
  updateCarouselLinkError: jest.fn(),
541
+ location: {},
542
+ tags: [],
543
+ injectedTags: {},
544
+ selectedOfferDetails: [],
545
+ handleOnTagsContextChange: jest.fn(),
398
546
  };
399
547
 
400
548
  const renderComponent = (props = {}) => {
@@ -515,7 +663,7 @@ describe('MediaUploaders', () => {
515
663
  onCarouselDataChange: mockOnCarouselDataChange,
516
664
  carouselActiveTabIndex: 0,
517
665
  activeTab: 'ANDROID',
518
- formatMessage: (message) => message.defaultMessage,
666
+ formatMessage: mockFormatMessage,
519
667
  });
520
668
 
521
669
  // Check if the carousel component is rendered
@@ -601,7 +749,7 @@ describe('MediaUploaders', () => {
601
749
  onCarouselDataChange: mockOnCarouselDataChange,
602
750
  carouselActiveTabIndex: 0,
603
751
  activeTab: 'ANDROID',
604
- formatMessage: (message) => message.defaultMessage,
752
+ formatMessage: mockFormatMessage,
605
753
  });
606
754
 
607
755
  // First check if the checkbox is checked
@@ -2057,6 +2057,11 @@ const MobilePushNew = ({
2057
2057
  updateHandler,
2058
2058
  deleteHandler,
2059
2059
  deepLink,
2060
+ location,
2061
+ tags,
2062
+ injectedTags,
2063
+ selectedOfferDetails,
2064
+ handleOnTagsContextChange,
2060
2065
  };
2061
2066
 
2062
2067
  const linkProps = {
@@ -2085,6 +2090,10 @@ const MobilePushNew = ({
2085
2090
  linkProps={linkProps}
2086
2091
  sameContent={sameContent}
2087
2092
  formatMessage={formatMessage}
2093
+ location={location}
2094
+ tags={tags}
2095
+ injectedTags={injectedTags}
2096
+ selectedOfferDetails={selectedOfferDetails}
2088
2097
  />
2089
2098
  );
2090
2099
  }, [androidContent, iosContent, androidTitleError, iosTitleError, androidMessageError, iosMessageError, androidExternalLinkError, iosExternalLinkError, androidDeepLinkError, iosDeepLinkError, androidDeepLinkKeysError, iosDeepLinkKeysError, formatMessage, activeTab, imageSrc, isFullMode, imageData, androidAssetList, iosAssetList, videoState, videoData, location, tags, injectedTags, selectedOfferDetails, primaryButtonAndroid, secondaryButtonAndroid, primaryButtonIos, secondaryButtonIos, ctaData, deepLink, mobilePushActions, carouselActiveTabIndex, carouselLinkErrors, handleTitleChange, handleMessageChange, handleMediaTypeChange, handleActionOnClickChange, handleLinkTypeChange, handleDeepLinkChange, handleDeepLinkKeysChange, handleExternalLinkChange, onTagSelect, handleOnTagsContextChange, setUpdateMpushImageSrc, updateOnMpushImageReUpload, setUpdateMpushVideoSrc, updateOnMpushVideoReUpload, clearImageDataByMediaType, handleCarouselDataChange, updateCarouselLinkError, sameContent, updateHandler, deleteHandler]
@@ -150,7 +150,8 @@
150
150
 
151
151
  .buttons-heading {
152
152
  margin-bottom: $CAP_SPACE_12;
153
- margin-top: $CAP_SPACE_16;
153
+ margin-top: $CAP_SPACE_12;
154
+ margin-right: 48%;
154
155
  }
155
156
 
156
157
  .helper-text {
@@ -111,7 +111,7 @@ export const Rcs = (props) => {
111
111
  isDltEnabled,
112
112
  smsRegister,
113
113
  selectedOfferDetails,
114
- currentOrgDetails
114
+ currentOrgDetails,
115
115
  } = props || {};
116
116
  const { formatMessage } = intl;
117
117
  const { TextArea } = CapInput;
@@ -189,6 +189,76 @@ export const Rcs = (props) => {
189
189
  display: flex;
190
190
  margin-top: 20px;
191
191
  `;
192
+
193
+ // tag Code start from here
194
+ useEffect(() => {
195
+ if (!showDltContainer) {
196
+ //fetching tags
197
+ const { type, module } = location.query || {};
198
+ const isEmbedded = type === EMBEDDED;
199
+ const context = isEmbedded ? module : DEFAULT;
200
+ const embedded = isEmbedded ? type : FULL;
201
+ const query = {
202
+ layout: SMS,
203
+ type: TAG,
204
+ context,
205
+ embedded,
206
+ };
207
+ if (getDefaultTags) {
208
+ query.context = getDefaultTags;
209
+ }
210
+ globalActions.fetchSchemaForEntity(query);
211
+ }
212
+ }, [showDltContainer]);
213
+
214
+ useEffect(() => {
215
+ let tag = get(metaEntities, `tags.standard`, []);
216
+ const { type, module } = location.query || {};
217
+ if (type === EMBEDDED && module === LIBRARY && !getDefaultTags) {
218
+ tag = supportedTags;
219
+ }
220
+ updateTags(tag);
221
+ }, [metaEntities]);
222
+
223
+ const handleOnTagsContextChange = (data) => {
224
+ const { type } = location.query || {};
225
+ const tempData = (data || '').toLowerCase();
226
+ const isEmbedded = type === EMBEDDED;
227
+ const embedded = isEmbedded ? type : FULL;
228
+ const context = tempData === ALL ? DEFAULT : tempData;
229
+ const query = {
230
+ layout: SMS,
231
+ type: TAG,
232
+ context,
233
+ embedded,
234
+ };
235
+ globalActions.fetchSchemaForEntity(query);
236
+ };
237
+
238
+ const onTagSelect = (data) => {
239
+ const tempMsg = `${templateDesc}{{${data}}}`;
240
+ const error = templateDescErrorHandler(tempMsg);
241
+ setTemplateDesc(tempMsg);
242
+ setTemplateDescError(error);
243
+ };
244
+
245
+ const onTagSelectFallback = (data) => {
246
+ const tempMsg = `${fallbackMessage}{{${data}}}`;
247
+ const error = fallbackMessageErrorHandler(tempMsg);
248
+ setFallbackMessage(tempMsg);
249
+ setFallbackMessageError(error);
250
+ };
251
+
252
+ //removing optout tag for rcs
253
+ const getRcsTags = () => {
254
+ const tempTags = cloneDeep(tags);
255
+ // eslint-disable-next-line no-undef
256
+ if (tempTags?.length > 0 && tempTags[0]?.definition?.value === 'optout') {
257
+ tempTags.shift();
258
+ }
259
+ return tempTags;
260
+ };
261
+ // tag Code end
192
262
  const rcsButtonTypeOptions = [
193
263
  {
194
264
  label: (
@@ -207,6 +277,12 @@ export const Rcs = (props) => {
207
277
  buttonTextlen={TEMPLATE_BUTTON_TEXT_MAX_LENGTH}
208
278
  updateButtonChange={updateButtonChange}
209
279
  suggestions={suggestions}
280
+ onTagSelect={onTagSelect}
281
+ location={location}
282
+ tags={getRcsTags()}
283
+ onContextChange={handleOnTagsContextChange}
284
+ injectedTags={injectedTags || {}}
285
+ selectedOfferDetails={selectedOfferDetails}
210
286
  />
211
287
  )}
212
288
  </>
@@ -306,76 +382,6 @@ export const Rcs = (props) => {
306
382
  }
307
383
  }, [rcsData.templateDetails || templateData]);
308
384
 
309
- // tag Code start from here
310
- useEffect(() => {
311
- if (!showDltContainer) {
312
- //fetching tags
313
- const { type, module } = location.query || {};
314
- const isEmbedded = type === EMBEDDED;
315
- const context = isEmbedded ? module : DEFAULT;
316
- const embedded = isEmbedded ? type : FULL;
317
- const query = {
318
- layout: SMS,
319
- type: TAG,
320
- context,
321
- embedded,
322
- };
323
- if (getDefaultTags) {
324
- query.context = getDefaultTags;
325
- }
326
- globalActions.fetchSchemaForEntity(query);
327
- }
328
- }, [showDltContainer]);
329
-
330
- useEffect(() => {
331
- let tag = get(metaEntities, `tags.standard`, []);
332
- const { type, module } = location.query || {};
333
- if (type === EMBEDDED && module === LIBRARY && !getDefaultTags) {
334
- tag = supportedTags;
335
- }
336
- updateTags(tag);
337
- }, [metaEntities]);
338
-
339
- const handleOnTagsContextChange = (data) => {
340
- const { type } = location.query || {};
341
- const tempData = (data || '').toLowerCase();
342
- const isEmbedded = type === EMBEDDED;
343
- const embedded = isEmbedded ? type : FULL;
344
- const context = tempData === ALL ? DEFAULT : tempData;
345
- const query = {
346
- layout: SMS,
347
- type: TAG,
348
- context,
349
- embedded,
350
- };
351
- globalActions.fetchSchemaForEntity(query);
352
- };
353
-
354
- const onTagSelect = (data) => {
355
- const tempMsg = `${templateDesc}{{${data}}}`;
356
- const error = templateDescErrorHandler(tempMsg);
357
- setTemplateDesc(tempMsg);
358
- setTemplateDescError(error);
359
- };
360
-
361
- const onTagSelectFallback = (data) => {
362
- const tempMsg = `${fallbackMessage}{{${data}}}`;
363
- const error = fallbackMessageErrorHandler(tempMsg);
364
- setFallbackMessage(tempMsg);
365
- setFallbackMessageError(error);
366
- };
367
-
368
- //removing optout tag for rcs
369
- const getRcsTags = () => {
370
- const tempTags = cloneDeep(tags);
371
- // eslint-disable-next-line no-undef
372
- if (tempTags?.length > 0 && tempTags[0]?.definition?.value === 'optout') {
373
- tempTags.shift();
374
- }
375
- return tempTags;
376
- };
377
- // tag Code end
378
-
379
385
  const renderLabel = (value, showLabel, desc) => (
380
386
  <>
381
387
  <RcsLabel>