@capillarytech/creatives-library 8.0.104 → 8.0.106-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/config/app.js CHANGED
@@ -17,16 +17,16 @@ const config = {
17
17
  accountConfig: (strs, accountId) => `${window.location.origin}/org/config/AccountAdd?q=a&channelId=2&accountId=${accountId}&edit=1`,
18
18
  },
19
19
  development: {
20
- api_endpoint: 'https://devenv-crm.cc.capillarytech.com/arya/api/v1/creatives',
21
- campaigns_api_endpoint: 'https://devenv-crm.cc.capillarytech.com/iris/v2/campaigns',
22
- campaigns_api_org_endpoint: 'https://devenv-crm.cc.capillarytech.com/iris/v2/org/campaign',
23
- auth_endpoint: 'https://devenv-crm.cc.capillarytech.com/arya/api/v1/auth',
24
- arya_endpoint: 'https://devenv-crm.cc.capillarytech.com/arya/api/v1',
25
- subscription_api_endpoint: 'https://devenv-crm.cc.capillarytech.com/arya/api/v1/org-settings/subscription',
26
- exports_api_endpoint: 'https://devenv-crm.cc.capillarytech.com/arya/api/v1/export/data',
20
+ api_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/arya/api/v1/creatives',
21
+ campaigns_api_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/iris/v2/campaigns',
22
+ campaigns_api_org_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/iris/v2/org/campaign',
23
+ auth_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/arya/api/v1/auth',
24
+ arya_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/arya/api/v1',
25
+ subscription_api_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/arya/api/v1/org-settings/subscription',
26
+ exports_api_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/arya/api/v1/export/data',
27
27
  login_url: '/auth/login',
28
28
  dashboard_url: '/sms',
29
- liquid_endpoint: 'https://devenv-crm.cc.capillarytech.com/iris/v2/template',
29
+ liquid_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/iris/v2/template',
30
30
  dashboard_url_v2: '/v2',
31
31
  },
32
32
  testing: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.104",
4
+ "version": "8.0.106-alpha.0",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -20,7 +20,7 @@ import globalMessages from '../../v2Containers/Cap/messages';
20
20
  import whatsappMsg from '../../v2Containers/Whatsapp/messages';
21
21
  import messages from './messages';
22
22
  import './index.scss';
23
- import { isUrl,isValidText } from '../../v2Containers/Line/Container/Wrapper/utils';
23
+ import { isUrl, isValidText } from '../../v2Containers/Line/Container/Wrapper/utils';
24
24
  import TagList from '../../v2Containers/TagList';
25
25
  import {
26
26
  CTA_OPTIONS,
@@ -50,6 +50,7 @@ export const CapWhatsappCTA = (props) => {
50
50
  tags = [],
51
51
  injectedTags = {},
52
52
  selectedOfferDetails = [],
53
+ eventContextTags = [],
53
54
  } = props;
54
55
  const { formatMessage } = intl;
55
56
  const invalidVarRegex = /{{(.*?)}}/g;
@@ -134,8 +135,7 @@ export const CapWhatsappCTA = (props) => {
134
135
  clonedCta.push(cloneDeep(INITIAL_CTA_DATA[0]));
135
136
  if (clonedCta.length === 2) {
136
137
  clonedCta[1].index = 1;
137
- clonedCta[1].ctaType =
138
- clonedCta[0].ctaType === PHONE_NUMBER ? WEBSITE : PHONE_NUMBER;
138
+ clonedCta[1].ctaType = clonedCta[0].ctaType === PHONE_NUMBER ? WEBSITE : PHONE_NUMBER;
139
139
  }
140
140
  updateHandler(clonedCta, null, true);
141
141
  };
@@ -168,8 +168,8 @@ export const CapWhatsappCTA = (props) => {
168
168
  if (!isUrl(value)) {
169
169
  errorMessage = formatMessage(messages.ctaWebsiteUrlErrorMessage);
170
170
  } else if (
171
- urlType === STATIC_URL &&
172
- value.match(invalidVarRegex)?.length > 0
171
+ urlType === STATIC_URL
172
+ && value.match(invalidVarRegex)?.length > 0
173
173
  ) {
174
174
  errorMessage = formatMessage(messages.staticUrlWithVarErrorMessage);
175
175
  }
@@ -178,12 +178,14 @@ export const CapWhatsappCTA = (props) => {
178
178
  };
179
179
 
180
180
  const ctaSaveDisabled = (index) => {
181
- const { ctaType, text, phone_number, url } = ctaData[index] || {};
181
+ const {
182
+ ctaType, text, phone_number, url,
183
+ } = ctaData[index] || {};
182
184
  if (text === '' || buttonError) {
183
185
  return true;
184
- } else if (ctaType === PHONE_NUMBER && phone_number.length < 5) {
186
+ } if (ctaType === PHONE_NUMBER && phone_number.length < 5) {
185
187
  return true;
186
- } else if (ctaType === WEBSITE && (url === '' || urlError)) {
188
+ } if (ctaType === WEBSITE && (url === '' || urlError)) {
187
189
  return true;
188
190
  }
189
191
  return false;
@@ -200,8 +202,9 @@ export const CapWhatsappCTA = (props) => {
200
202
  const renderArray = [];
201
203
  const addBtnDisabled = ctaData.length === 1 && !ctaData[0].isSaved;
202
204
  ctaData.forEach((cta) => {
203
- const { index, ctaType, text, phone_number, urlType, url, isSaved } =
204
- cta || {};
205
+ const {
206
+ index, ctaType, text, phone_number, urlType, url, isSaved,
207
+ } = cta || {};
205
208
  //this is to display buttons after they are saved, in both create and edit mode.
206
209
  if (isSaved) {
207
210
  const ctaIsPhone = ctaType === PHONE_NUMBER;
@@ -229,7 +232,10 @@ export const CapWhatsappCTA = (props) => {
229
232
  </CapColumn>
230
233
  {ctaIsPhone && (
231
234
  <CapColumn span={10} align="left">
232
- <CapLabel className="phone">+{phone_number}</CapLabel>
235
+ <CapLabel className="phone">
236
+ +
237
+ {phone_number}
238
+ </CapLabel>
233
239
  </CapColumn>
234
240
  )}
235
241
  {!ctaIsPhone && (
@@ -241,7 +247,7 @@ export const CapWhatsappCTA = (props) => {
241
247
  : formatMessage(messages.ctaWebsiteTypeDynamic)}
242
248
  </CapLabel>
243
249
  </CapColumn>
244
- <CapTooltip title={url} placement={'top'}>
250
+ <CapTooltip title={url} placement="top">
245
251
  <CapColumn span={7} style={{ marginRight: '0px' }}>
246
252
  <CapLabel className="url">{url}</CapLabel>
247
253
  </CapColumn>
@@ -271,6 +277,7 @@ export const CapWhatsappCTA = (props) => {
271
277
  tags={tags}
272
278
  injectedTags={injectedTags}
273
279
  selectedOfferDetails={selectedOfferDetails}
280
+ eventContextTags={eventContextTags}
274
281
  />
275
282
  </CapColumn>
276
283
  )}
@@ -282,7 +289,7 @@ export const CapWhatsappCTA = (props) => {
282
289
  >
283
290
  <CapTooltip
284
291
  title={formatMessage(messages.whatsappCtaTagListRevert)}
285
- placement={'top'}
292
+ placement="top"
286
293
  >
287
294
  <CapIcon size="s" type="return" />
288
295
  </CapTooltip>
@@ -414,7 +421,7 @@ export const CapWhatsappCTA = (props) => {
414
421
  ? formatMessage(messages.ctaSaveDisabled)
415
422
  : ''
416
423
  }
417
- placement={'bottom'}
424
+ placement="bottom"
418
425
  >
419
426
  <div className="button-disabled-tooltip-wrapper">
420
427
  <CapButton
@@ -442,15 +449,15 @@ export const CapWhatsappCTA = (props) => {
442
449
  {
443
450
  // when first button is not saved, add button is disabled with tooltip
444
451
  // eslint-disable-next-line no-unused-expressions
445
- ctaData.length < 2 &&
446
- !isEditFlow &&
447
- renderArray.push(
452
+ ctaData.length < 2
453
+ && !isEditFlow
454
+ && renderArray.push(
448
455
  <CapRow>
449
456
  <CapTooltip
450
457
  title={
451
458
  addBtnDisabled ? formatMessage(messages.ctaAddDisabled) : ''
452
459
  }
453
- placement={'right'}
460
+ placement="right"
454
461
  >
455
462
  <div className="button-disabled-tooltip-wrapper">
456
463
  <CapButton
@@ -180,7 +180,7 @@ EmailWrapperView.propTypes = {
180
180
  isTemplateNameEmpty: PropTypes.bool,
181
181
  modes: PropTypes.array.isRequired,
182
182
  onChange: PropTypes.func.isRequired,
183
- EmailLayout: PropTypes.string,
183
+ EmailLayout: PropTypes.object,
184
184
  modeContent: PropTypes.object,
185
185
  useFileUpload: PropTypes.func.isRequired,
186
186
  uploadButtonLabel: PropTypes.node.isRequired,
@@ -1,6 +1,7 @@
1
1
  import { useState, useEffect, useMemo, useCallback } from 'react';
2
2
  import isEmpty from 'lodash/isEmpty';
3
3
  import get from 'lodash/get';
4
+ import find from 'lodash/find';
4
5
  import { GA } from '@capillarytech/cap-ui-utils';
5
6
  import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
6
7
  import { CHANNEL_CREATE_TRACK_MAPPING } from '../../App/constants';
@@ -367,41 +367,83 @@ describe('useEmailWrapper', () => {
367
367
  });
368
368
 
369
369
  it('calls setEdmTemplate/setBEETemplate in useEffect when in EDITOR mode and CREATE_TEMPLATE_CONTENT step', async () => {
370
+ // 1. Setup
370
371
  const templateId = 'editorTemplate456';
371
372
  const mockTemplateData = { _id: templateId, name: 'Selected Editor Template' };
372
- find.mockReturnValue(mockTemplateData);
373
373
 
374
- const initialHookProps = {
374
+ // Ensure mocks are clean from previous tests
375
+ find.mockClear();
376
+ isEmpty.mockClear();
377
+ mockProps.templatesActions.setEdmTemplate.mockClear();
378
+ mockProps.templatesActions.setBEETemplate.mockClear();
379
+ mockProps.showNextStep.mockClear();
380
+
381
+ // 2. Mock find (this will be called by the *internal* handleEdmDefaultTemplateSelection)
382
+ find.mockImplementation((array, criteria) => {
383
+ // console.log('DEBUG: find called with', JSON.stringify(array), JSON.stringify(criteria)); // Optional debug
384
+ if (array && criteria && criteria._id === templateId && array[0]?._id === templateId) {
385
+ return mockTemplateData;
386
+ }
387
+ return null;
388
+ });
389
+
390
+ // 3. Initial Render (Template Selection Step)
391
+ const initialProps = {
375
392
  ...mockProps,
376
393
  step: STEPS.TEMPLATE_SELECTION,
377
394
  emailCreateMode: EMAIL_CREATE_MODES.EDITOR,
378
395
  CmsTemplates: [mockTemplateData],
379
396
  SelectedEdmDefaultTemplate: null,
397
+ // selectedCreateMode is initially '' from hook's useState
380
398
  };
381
-
382
399
  const { result, rerender } = renderHook((props) => useEmailWrapper(props), {
383
- initialProps: initialHookProps
400
+ initialProps: initialProps
384
401
  });
385
402
 
403
+ // 4. Simulate user selecting a template ID via the exposed callback (useEditor)
386
404
  reactAct(() => {
405
+ // This maps to useEditor which calls setModeContent and showNextStep
387
406
  result.current.cmsTemplatesProps.handleEdmDefaultTemplateSelection(templateId);
388
407
  });
389
-
390
- expect(result.current.modeContent).toEqual({ id: templateId });
391
- expect(mockProps.showNextStep).toHaveBeenCalled();
392
-
393
- const nextStepProps = {
394
- ...initialHookProps,
395
- step: STEPS.CREATE_TEMPLATE_CONTENT,
408
+ // Verify internal state updated and mock called
409
+ expect(result.current.modeContent).toEqual({ id: templateId }); // State updated
410
+ expect(mockProps.showNextStep).toHaveBeenCalledTimes(1); // User action effect
411
+
412
+ // 5. Rerender with step changed to CREATE_TEMPLATE_CONTENT
413
+ const createContentProps = {
414
+ ...initialProps, // Base props
415
+ step: STEPS.CREATE_TEMPLATE_CONTENT, // *** Change step ***
416
+ SelectedEdmDefaultTemplate: null // *** Ensure this is null ***
417
+ // The internal state `modeContent = { id: templateId }` persists
418
+ // The internal state `selectedCreateMode = ''` also persists (not changed yet)
396
419
  };
397
420
 
398
- rerender(nextStepProps);
421
+ // Mock isEmpty just before rerender for the specific check inside the effect
422
+ isEmpty.mockImplementation(val => {
423
+ // console.log('DEBUG: isEmpty called with', JSON.stringify(val)); // Optional debug
424
+ // Return true ONLY for the 'SelectedEdmDefaultTemplate' check (which is null)
425
+ if (val === null) return true;
426
+ // Provide a fallback for other potential isEmpty calls if needed
427
+ if (typeof val === 'object' && !val) return true;
428
+ if (typeof val === 'object' && Object.keys(val).length === 0) return true;
429
+ if (typeof val === 'string' && val.trim().length === 0) return true;
430
+ return !val;
431
+ });
432
+
433
+ rerender(createContentProps);
399
434
 
400
- await waitFor(() => {
401
- expect(find).toHaveBeenCalledWith([mockTemplateData], { _id: templateId });
402
- expect(mockProps.templatesActions.setEdmTemplate).toHaveBeenCalledWith(mockTemplateData);
403
- expect(mockProps.templatesActions.setBEETemplate).toHaveBeenCalledWith(mockTemplateData);
435
+ // 6. Wait for effects triggered by the rerender
436
+ await reactAct(async () => {
437
+ // Short pause allows React's scheduler to run effects
438
+ await new Promise(resolve => setTimeout(resolve, 0));
404
439
  });
440
+
441
+
442
+ // Assert that the template setting actions were called as a result of the effect
443
+ expect(mockProps.templatesActions.setEdmTemplate).toHaveBeenCalledTimes(1);
444
+ expect(mockProps.templatesActions.setEdmTemplate).toHaveBeenCalledWith(mockTemplateData);
445
+ expect(mockProps.templatesActions.setBEETemplate).toHaveBeenCalledTimes(1);
446
+ expect(mockProps.templatesActions.setBEETemplate).toHaveBeenCalledWith(mockTemplateData);
405
447
  });
406
448
 
407
449
  it('sets selectedCreateMode in useEffect when in UPLOAD mode and CREATE_TEMPLATE_CONTENT step with EmailLayout', async () => {
@@ -7,6 +7,7 @@
7
7
  import PropTypes from 'prop-types';
8
8
 
9
9
  import React from 'react';
10
+ import { injectIntl } from 'react-intl';
10
11
  import { connect } from 'react-redux';
11
12
  import { Tree } from 'antd';
12
13
  import _ from 'lodash';
@@ -16,17 +17,20 @@ import makeSelectTagList from './selectors';
16
17
  import { UserIsAuthenticated } from '../../utils/authWrapper';
17
18
  import * as actions from './actions';
18
19
  import * as globalActions from '../Cap/actions';
20
+ import messages, { scope } from './messages';
21
+
19
22
  // import styled from styled-components;
20
23
  import CapTagList from '../../v2Components/CapTagList';
21
24
  import './_tagList.scss';
22
25
  import { selectCurrentOrgDetails, makeSelectFetchingSchemaError } from '../Cap/selectors';
23
- import { injectIntl } from 'react-intl';
24
- import { scope } from './messages';
25
- import messages from './messages';
26
- import { handleInjectedData, hasGiftVoucherFeature, hasPromoFeature, hasBadgesFeature, transformBadgeTags } from '../../utils/common';
27
- import { GIFT_VOUCHER_RELATED_TAGS, PROMO_ENGINE_RELATED_TAGS, BADGES_RELATED_TAGS, BADGES_ENROLL, BADGES_ISSUE } from '../../containers/App/constants';
26
+ import {
27
+ handleInjectedData, hasGiftVoucherFeature, hasPromoFeature, hasBadgesFeature, transformBadgeTags,
28
+ } from '../../utils/common';
29
+ import {
30
+ GIFT_VOUCHER_RELATED_TAGS, PROMO_ENGINE_RELATED_TAGS, BADGES_RELATED_TAGS, BADGES_ENROLL, BADGES_ISSUE,
31
+ } from '../../containers/App/constants';
28
32
 
29
- const TreeNode = Tree.TreeNode;
33
+ const {TreeNode} = Tree;
30
34
 
31
35
  export class TagList extends React.Component { // eslint-disable-line react/prefer-stateless-function
32
36
  constructor(props) {
@@ -52,35 +56,35 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
52
56
  this.setState({loading: true});
53
57
  }
54
58
  if (!_.isEqual(nextProps.injectedTags, this.props.injectedTags)) {
55
-
56
59
  this.setState({loading: false});
57
60
  }
58
61
  if (!_.isEqual(nextProps.tags, this.props.tags)) {
59
-
60
62
  this.setState({loading: false});
61
63
  }
62
64
  if (nextProps?.fetchingSchemaError) {
63
65
  this.setState({tagsError: nextProps?.fetchingSchemaError});
64
66
  }
65
-
66
67
  }
68
+
67
69
  componentDidUpdate(prevProps) {
68
70
  if (this.props.tags !== prevProps.tags || this.props.injectedTags !== prevProps.injectedTags || this.props.selectedOfferDetails !== prevProps.selectedOfferDetails) {
69
71
  this.generateTags(this.props);
70
72
  }
71
73
  }
74
+
72
75
  onSelect = (selectedKeys) => {
73
76
  this.props.onTagSelect(selectedKeys[0]);
74
77
  };
75
78
 
76
- onClick = (e, data) => {
79
+ // onClick = (e, data) => {
77
80
 
78
- };
81
+ // };
79
82
 
80
83
  getTagsforContext = (data) => {
81
84
  //this.setState({loading: true});
82
85
  this.props.onContextChange(data);
83
86
  }
87
+
84
88
  generateTags = (props) => {
85
89
  let tags = {};
86
90
  let injectedTags = {};
@@ -108,24 +112,26 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
108
112
  if (eventContextTags?.length) {
109
113
  const TAG_HEADER_MSG_LABEL = this.props.intl.formatMessage(messages.entryEvent);
110
114
  eventContextTagsObj.eventContextTags = {
111
- name: TAG_HEADER_MSG_LABEL,
112
- desc: TAG_HEADER_MSG_LABEL,
113
- resolved: true,
115
+ "name": TAG_HEADER_MSG_LABEL,
116
+ "desc": TAG_HEADER_MSG_LABEL,
117
+ "resolved": true,
114
118
  'tag-header': true,
115
- subtags: {},
119
+ "subtags": {},
116
120
  };
117
121
 
118
122
  eventContextTags.forEach((tag) => {
119
- const { tagName, label, profileId, profileName } = tag || {};
123
+ const {
124
+ tagName, label, profileId, profileName,
125
+ } = tag || {};
120
126
  if (!profileId || !tagName || !label || !profileName) return;
121
127
  // Initializing the tags profile if it doesn't exist
122
128
  if (!eventContextTagsObj?.eventContextTags?.subtags?.[profileId]) {
123
129
  eventContextTagsObj.eventContextTags.subtags[profileId] = {
124
- name: profileName,
125
- desc: profileName,
126
- resolved: true,
130
+ "name": profileName,
131
+ "desc": profileName,
132
+ "resolved": true,
127
133
  "tag-header": true,
128
- subtags: {},
134
+ "subtags": {},
129
135
  };
130
136
  }
131
137
  // Adding the current tag to the profile group
@@ -138,6 +144,7 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
138
144
  }
139
145
  this.setState({tags: _.merge( {}, tags, injectedTags, eventContextTagsObj )});
140
146
  }
147
+
141
148
  populateTags(tagsList) {
142
149
  const mainTags = {};
143
150
  const excludedTags = [];
@@ -164,8 +171,8 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
164
171
  }
165
172
  if (!tag['tag-header']) {
166
173
  mainTags[tag.value] = {
167
- "name": tag?.label[userLocale] ? tag?.label[userLocale] : tag?.label?.en,
168
- "desc": tag?.label[userLocale] ? tag?.label[userLocale] : tag?.label?.en,
174
+ name: tag?.label[userLocale] ? tag?.label[userLocale] : tag?.label?.en,
175
+ desc: tag?.label[userLocale] ? tag?.label[userLocale] : tag?.label?.en,
169
176
  };
170
177
  } else if (tag['tag-header'] && mainTags[tag.value]) {
171
178
  mainTags[tag.value].subtags = _.concat(mainTags[tag.value].subtags, tag.subtags);
@@ -245,7 +252,9 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
245
252
  };
246
253
  };
247
254
  offerDetails.forEach((offer) => {
248
- const { id, couponName, description, couponSeriesId } = offer;
255
+ const {
256
+ id, couponName, description, couponSeriesId,
257
+ } = offer;
249
258
  const couponId = id || couponSeriesId;
250
259
  const couponSubTags = {};
251
260
  couponTagsKeys.forEach((couponTagKey) => {
@@ -879,6 +879,10 @@ const isAuthenticationTemplate = isEqual(templateCategory, WHATSAPP_CATEGORIES.a
879
879
  deleteHandler={deleteHandler}
880
880
  isEditFlow={isEditFlow}
881
881
  hostName={host}
882
+ tags={tags || []}
883
+ injectedTags={injectedTags || {}}
884
+ selectedOfferDetails={selectedOfferDetails}
885
+ eventContextTags={eventContextTags}
882
886
  />
883
887
  )}
884
888
  </>
@@ -2694,6 +2698,7 @@ const isAuthenticationTemplate = isEqual(templateCategory, WHATSAPP_CATEGORIES.a
2694
2698
  tags={tags || []}
2695
2699
  injectedTags={injectedTags || {}}
2696
2700
  selectedOfferDetails={selectedOfferDetails}
2701
+ eventContextTags={eventContextTags}
2697
2702
  />
2698
2703
  )}
2699
2704
  {isBtnTypeQuickReply && (