@capillarytech/creatives-library 8.0.347 → 8.0.348

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 (38) hide show
  1. package/package.json +1 -1
  2. package/services/api.js +0 -20
  3. package/services/tests/api.test.js +0 -59
  4. package/utils/tests/v2Common.test.js +1 -46
  5. package/utils/v2common.js +0 -18
  6. package/v2Components/CapCustomSkeleton/index.js +1 -1
  7. package/v2Components/CapCustomSkeleton/tests/__snapshots__/index.test.js.snap +12 -12
  8. package/v2Components/CommonTestAndPreview/UnifiedPreview/WhatsAppPreviewContent.js +18 -6
  9. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +27 -0
  10. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WhatsAppPreviewContent.test.js +48 -0
  11. package/v2Components/TemplatePreview/_templatePreview.scss +21 -0
  12. package/v2Components/TemplatePreview/index.js +18 -6
  13. package/v2Components/TemplatePreview/tests/__snapshots__/index.test.js.snap +1 -0
  14. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -3
  15. package/v2Containers/CreativesContainer/index.js +9 -6
  16. package/v2Containers/CreativesContainer/messages.js +0 -4
  17. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +0 -3
  18. package/v2Containers/Templates/ChannelTypeIllustration.js +6 -23
  19. package/v2Containers/Templates/_templates.scss +24 -179
  20. package/v2Containers/Templates/actions.js +0 -44
  21. package/v2Containers/Templates/constants.js +0 -23
  22. package/v2Containers/Templates/index.js +58 -378
  23. package/v2Containers/Templates/messages.js +0 -88
  24. package/v2Containers/Templates/reducer.js +1 -84
  25. package/v2Containers/Templates/sagas.js +0 -64
  26. package/v2Containers/Templates/selectors.js +0 -12
  27. package/v2Containers/Templates/tests/ChannelTypeIllustration.test.js +0 -12
  28. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1122 -1345
  29. package/v2Containers/Templates/tests/index.test.js +0 -6
  30. package/v2Containers/Templates/tests/reducer.test.js +0 -178
  31. package/v2Containers/Templates/tests/sagas.test.js +8 -390
  32. package/v2Containers/Templates/tests/selector.test.js +0 -32
  33. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -1
  34. package/v2Containers/Whatsapp/constants.js +8 -0
  35. package/v2Containers/Whatsapp/index.js +142 -5
  36. package/v2Containers/Whatsapp/index.scss +8 -0
  37. package/v2Containers/Whatsapp/messages.js +16 -0
  38. package/v2Containers/Assets/images/archive_Empty_Illustration.svg +0 -9
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.347",
4
+ "version": "8.0.348",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/services/api.js CHANGED
@@ -361,26 +361,6 @@ export const deleteTemplate = ({channel, id}) => {
361
361
  //return API.deleteResource(url);
362
362
  };
363
363
 
364
- export const archiveTemplate = ({ channel, id }) => {
365
- const url = `${API_ENDPOINT}/templates/archive/${id}/${channel}`;
366
- return request(url, getAPICallObject('PUT'));
367
- };
368
-
369
- export const unarchiveTemplate = ({ channel, id }) => {
370
- const url = `${API_ENDPOINT}/templates/unarchive/${id}/${channel}`;
371
- return request(url, getAPICallObject('PUT'));
372
- };
373
-
374
- export const bulkArchiveTemplates = ({ channel, ids }) => {
375
- const url = `${API_ENDPOINT}/templates/archive/bulk/${channel}`;
376
- return request(url, getAPICallObject('PUT', { ids }));
377
- };
378
-
379
- export const bulkUnarchiveTemplates = ({ channel, ids }) => {
380
- const url = `${API_ENDPOINT}/templates/unarchive/bulk/${channel}`;
381
- return request(url, getAPICallObject('PUT', { ids }));
382
- };
383
-
384
364
  export const deleteRcsTemplate = ({ templateName }) => {
385
365
  const url = `${API_ENDPOINT}/templates/${templateName}/RCS`;
386
366
  return request(url, getAPICallObject('DELETE'))
@@ -1086,62 +1086,3 @@ describe('createTestCustomer', () => {
1086
1086
  expect(lastCall[1].body).toBe(JSON.stringify(payload));
1087
1087
  });
1088
1088
  });
1089
-
1090
- import {
1091
- archiveTemplate,
1092
- unarchiveTemplate,
1093
- bulkArchiveTemplates,
1094
- bulkUnarchiveTemplates,
1095
- } from '../api';
1096
-
1097
- describe('archiveTemplate', () => {
1098
- beforeEach(() => {
1099
- global.fetch = jest.fn().mockReturnValue(Promise.resolve({ json: () => Promise.resolve({}) }));
1100
- });
1101
-
1102
- it('should call PUT on archive endpoint', () => {
1103
- archiveTemplate({ channel: 'EMAIL', id: 'id123' });
1104
- expect(global.fetch).toHaveBeenCalled();
1105
- const lastCall = global.fetch.mock.calls[global.fetch.mock.calls.length - 1];
1106
- expect(lastCall[1].method).toBe('PUT');
1107
- });
1108
- });
1109
-
1110
- describe('unarchiveTemplate', () => {
1111
- beforeEach(() => {
1112
- global.fetch = jest.fn().mockReturnValue(Promise.resolve({ json: () => Promise.resolve({}) }));
1113
- });
1114
-
1115
- it('should call PUT on unarchive endpoint', () => {
1116
- unarchiveTemplate({ channel: 'EMAIL', id: 'id123' });
1117
- expect(global.fetch).toHaveBeenCalled();
1118
- const lastCall = global.fetch.mock.calls[global.fetch.mock.calls.length - 1];
1119
- expect(lastCall[1].method).toBe('PUT');
1120
- });
1121
- });
1122
-
1123
- describe('bulkArchiveTemplates', () => {
1124
- beforeEach(() => {
1125
- global.fetch = jest.fn().mockReturnValue(Promise.resolve({ json: () => Promise.resolve({}) }));
1126
- });
1127
-
1128
- it('should call PUT on bulk archive endpoint with ids', () => {
1129
- bulkArchiveTemplates({ channel: 'EMAIL', ids: ['id1', 'id2'] });
1130
- expect(global.fetch).toHaveBeenCalled();
1131
- const lastCall = global.fetch.mock.calls[global.fetch.mock.calls.length - 1];
1132
- expect(lastCall[1].method).toBe('PUT');
1133
- });
1134
- });
1135
-
1136
- describe('bulkUnarchiveTemplates', () => {
1137
- beforeEach(() => {
1138
- global.fetch = jest.fn().mockReturnValue(Promise.resolve({ json: () => Promise.resolve({}) }));
1139
- });
1140
-
1141
- it('should call PUT on bulk unarchive endpoint with ids', () => {
1142
- bulkUnarchiveTemplates({ channel: 'EMAIL', ids: ['id1', 'id2'] });
1143
- expect(global.fetch).toHaveBeenCalled();
1144
- const lastCall = global.fetch.mock.calls[global.fetch.mock.calls.length - 1];
1145
- expect(lastCall[1].method).toBe('PUT');
1146
- });
1147
- });
@@ -1,6 +1,5 @@
1
1
  import React from 'react';
2
- import { shallow } from 'enzyme';
3
- import { getObjFromQueryParams, buildTemplateNameDescription } from '../v2common';
2
+ import { getObjFromQueryParams } from '../v2common';
4
3
 
5
4
  describe('Test v2common container', () => {
6
5
  it('test getObjFromQueryParams', () => {
@@ -10,47 +9,3 @@ describe('Test v2common container', () => {
10
9
  });
11
10
  });
12
11
  });
13
-
14
- describe('buildTemplateNameDescription', () => {
15
- it('returns undefined when templateName is empty string', () => {
16
- expect(buildTemplateNameDescription('Template name', '')).toBeUndefined();
17
- });
18
-
19
- it('returns undefined when templateName is null', () => {
20
- expect(buildTemplateNameDescription('Template name', null)).toBeUndefined();
21
- });
22
-
23
- it('returns undefined when templateName is undefined', () => {
24
- expect(buildTemplateNameDescription('Template name', undefined)).toBeUndefined();
25
- });
26
-
27
- it('renders a span wrapper when templateName is provided', () => {
28
- const result = buildTemplateNameDescription('Template name', 'Welcome 01');
29
- const wrapper = shallow(result);
30
- expect(wrapper.type()).toBe('span');
31
- });
32
-
33
- it('renders label text in the first CapLabelInline with correct class', () => {
34
- const result = buildTemplateNameDescription('Template name', 'Welcome 01');
35
- const wrapper = shallow(result);
36
- const firstLabel = wrapper.childAt(0);
37
- expect(firstLabel.prop('className')).toBe('notification-template-label');
38
- expect(firstLabel.prop('children')).toBe('Template name');
39
- });
40
-
41
- it('renders template name in the second CapLabelInline with correct class', () => {
42
- const result = buildTemplateNameDescription('Template name', 'Welcome 01');
43
- const wrapper = shallow(result);
44
- const secondLabel = wrapper.childAt(1);
45
- expect(secondLabel.prop('className')).toBe('notification-template-name');
46
- expect(secondLabel.prop('children')).toBe('Welcome 01');
47
- });
48
-
49
- it('both CapLabelInline elements have type="label1"', () => {
50
- const result = buildTemplateNameDescription('Template name', 'Welcome 01');
51
- const wrapper = shallow(result);
52
- wrapper.children().forEach((node) => {
53
- expect(node.prop('type')).toBe('label1');
54
- });
55
- });
56
- });
package/utils/v2common.js CHANGED
@@ -1,21 +1,3 @@
1
- import React from 'react';
2
- import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
3
-
4
- /**
5
- * Builds a JSX description for template archive/unarchive notifications.
6
- * @param {string} labelText - Localised label (e.g. "Template name ")
7
- * @param {string} templateName - The template name to display
8
- */
9
- export const buildTemplateNameDescription = (labelText, templateName) => (
10
- templateName
11
- ? (
12
- <span>
13
- <CapLabel.CapLabelInline type="label1" className="notification-template-label">{labelText}</CapLabel.CapLabelInline>
14
- <CapLabel.CapLabelInline type="label1" className="notification-template-name">{templateName}</CapLabel.CapLabelInline>
15
- </span>
16
- )
17
- : undefined
18
- );
19
1
 
20
2
  // returns query items as obj when query string is passed
21
3
  /**
@@ -28,7 +28,7 @@ export default function CapCustomSkeleton(props) {
28
28
  xs={24}
29
29
  sm={12}
30
30
  md={8}
31
- lg={8}
31
+ lg={6}
32
32
  >
33
33
  <CapSkeleton
34
34
  active
@@ -20,7 +20,7 @@ exports[`CapCustomSkeleton test renders correct CapCustomSkeleton component 1`]
20
20
  >
21
21
  <CapColumn
22
22
  key="0"
23
- lg={8}
23
+ lg={6}
24
24
  md={8}
25
25
  sm={12}
26
26
  xs={24}
@@ -40,7 +40,7 @@ exports[`CapCustomSkeleton test renders correct CapCustomSkeleton component 1`]
40
40
  </CapColumn>
41
41
  <CapColumn
42
42
  key="1"
43
- lg={8}
43
+ lg={6}
44
44
  md={8}
45
45
  sm={12}
46
46
  xs={24}
@@ -60,7 +60,7 @@ exports[`CapCustomSkeleton test renders correct CapCustomSkeleton component 1`]
60
60
  </CapColumn>
61
61
  <CapColumn
62
62
  key="2"
63
- lg={8}
63
+ lg={6}
64
64
  md={8}
65
65
  sm={12}
66
66
  xs={24}
@@ -80,7 +80,7 @@ exports[`CapCustomSkeleton test renders correct CapCustomSkeleton component 1`]
80
80
  </CapColumn>
81
81
  <CapColumn
82
82
  key="3"
83
- lg={8}
83
+ lg={6}
84
84
  md={8}
85
85
  sm={12}
86
86
  xs={24}
@@ -100,7 +100,7 @@ exports[`CapCustomSkeleton test renders correct CapCustomSkeleton component 1`]
100
100
  </CapColumn>
101
101
  <CapColumn
102
102
  key="4"
103
- lg={8}
103
+ lg={6}
104
104
  md={8}
105
105
  sm={12}
106
106
  xs={24}
@@ -120,7 +120,7 @@ exports[`CapCustomSkeleton test renders correct CapCustomSkeleton component 1`]
120
120
  </CapColumn>
121
121
  <CapColumn
122
122
  key="5"
123
- lg={8}
123
+ lg={6}
124
124
  md={8}
125
125
  sm={12}
126
126
  xs={24}
@@ -140,7 +140,7 @@ exports[`CapCustomSkeleton test renders correct CapCustomSkeleton component 1`]
140
140
  </CapColumn>
141
141
  <CapColumn
142
142
  key="6"
143
- lg={8}
143
+ lg={6}
144
144
  md={8}
145
145
  sm={12}
146
146
  xs={24}
@@ -160,7 +160,7 @@ exports[`CapCustomSkeleton test renders correct CapCustomSkeleton component 1`]
160
160
  </CapColumn>
161
161
  <CapColumn
162
162
  key="7"
163
- lg={8}
163
+ lg={6}
164
164
  md={8}
165
165
  sm={12}
166
166
  xs={24}
@@ -180,7 +180,7 @@ exports[`CapCustomSkeleton test renders correct CapCustomSkeleton component 1`]
180
180
  </CapColumn>
181
181
  <CapColumn
182
182
  key="8"
183
- lg={8}
183
+ lg={6}
184
184
  md={8}
185
185
  sm={12}
186
186
  xs={24}
@@ -200,7 +200,7 @@ exports[`CapCustomSkeleton test renders correct CapCustomSkeleton component 1`]
200
200
  </CapColumn>
201
201
  <CapColumn
202
202
  key="9"
203
- lg={8}
203
+ lg={6}
204
204
  md={8}
205
205
  sm={12}
206
206
  xs={24}
@@ -220,7 +220,7 @@ exports[`CapCustomSkeleton test renders correct CapCustomSkeleton component 1`]
220
220
  </CapColumn>
221
221
  <CapColumn
222
222
  key="10"
223
- lg={8}
223
+ lg={6}
224
224
  md={8}
225
225
  sm={12}
226
226
  xs={24}
@@ -240,7 +240,7 @@ exports[`CapCustomSkeleton test renders correct CapCustomSkeleton component 1`]
240
240
  </CapColumn>
241
241
  <CapColumn
242
242
  key="11"
243
- lg={8}
243
+ lg={6}
244
244
  md={8}
245
245
  sm={12}
246
246
  xs={24}
@@ -18,7 +18,7 @@ import CapRow from '@capillarytech/cap-ui-library/CapRow';
18
18
  import { ANDROID, IOS } from '../constants';
19
19
  import messages from '../messages';
20
20
  import { getWhatsappQuickReply, getWhatsappCarouselButtonView } from '../../../v2Containers/Whatsapp/utils';
21
- import { QUICK_REPLY, PHONE_NUMBER, WHATSAPP_CATEGORIES } from '../../../v2Containers/Whatsapp/constants';
21
+ import { QUICK_REPLY, PHONE_NUMBER, WHATSAPP_CATEGORIES, TEMPLATE_VARIABLE_REGEX } from '../../../v2Containers/Whatsapp/constants';
22
22
  import videoPlay from '../../../assets/videoPlay.svg';
23
23
  import whatsappImageEmptyPreview from '../../TemplatePreview/assets/images/empty_image_preview.svg';
24
24
  import whatsappVideoEmptyPreview from '../../TemplatePreview/assets/images/empty_video_preview.svg';
@@ -27,6 +27,8 @@ import whatsappVideoEmptyPreview from '../../TemplatePreview/assets/images/empty
27
27
  const whatsappMobileAndroid = require('../../../assets/whatsapp_android.png');
28
28
  const whatsappMobileIos = require('../../../assets/whatsapp_ios.png');
29
29
 
30
+ const { CapLabelInline } = CapLabel;
31
+
30
32
  const WhatsAppPreviewContent = ({
31
33
  content,
32
34
  device,
@@ -214,11 +216,21 @@ const WhatsAppPreviewContent = ({
214
216
 
215
217
  {/* Image Preview */}
216
218
  {content?.whatsappImageSrc && (
217
- <CapImage
218
- src={content.whatsappImageSrc}
219
- className="whatsapp-image"
220
- alt={formatMessage(messages.previewGenerated)}
221
- />
219
+ TEMPLATE_VARIABLE_REGEX.test(content.whatsappImageSrc) ? (
220
+ <CapRow className="whatsapp-image-url-placeholder">
221
+ <CapIcon type="picture" size="s" className="whatsapp-image-url-placeholder-icon" />
222
+ <CapLabelInline type="label2" className="whatsapp-image-url-placeholder-text">
223
+ {content.whatsappImageSrc}
224
+ </CapLabelInline>
225
+ </CapRow>
226
+ ) : (
227
+ <CapImage
228
+ src={content.whatsappImageSrc}
229
+ className="whatsapp-image"
230
+ alt={formatMessage(messages.previewGenerated)}
231
+ onError={(e) => { e.target.src = whatsappImageEmptyPreview; }}
232
+ />
233
+ )
222
234
  )}
223
235
 
224
236
  {/* Video Preview */}
@@ -940,6 +940,33 @@
940
940
  }
941
941
  }
942
942
 
943
+ .whatsapp-image-url-placeholder {
944
+ display: flex;
945
+ flex-direction: column;
946
+ align-items: center;
947
+ justify-content: center;
948
+ gap: $CAP_SPACE_08;
949
+ width: 100%;
950
+ min-height: 5rem;
951
+ max-height: 8.786rem;
952
+ margin-bottom: $CAP_SPACE_04;
953
+ background-color: $CAP_G09;
954
+ border-radius: $CAP_SPACE_04;
955
+ padding: $CAP_SPACE_08;
956
+
957
+ .whatsapp-image-url-placeholder-icon {
958
+ color: $CAP_G05;
959
+ font-size: 1.5rem;
960
+ }
961
+
962
+ .whatsapp-image-url-placeholder-text {
963
+ font-size: $FONT_SIZE_S;
964
+ color: $CAP_G04;
965
+ text-align: center;
966
+ word-break: break-all;
967
+ }
968
+ }
969
+
943
970
  .whatsapp-image {
944
971
  width: 100%;
945
972
  max-height: 8.786rem;
@@ -26,6 +26,7 @@ jest.mock('../../../../v2Containers/Whatsapp/constants', () => ({
26
26
  WHATSAPP_CATEGORIES: {
27
27
  authentication: 'authentication',
28
28
  },
29
+ TEMPLATE_VARIABLE_REGEX: /\{\{.*?\}\}/,
29
30
  }));
30
31
 
31
32
  // Convert messages object to format expected by IntlProvider
@@ -238,6 +239,53 @@ describe('WhatsAppPreviewContent', () => {
238
239
  expect(image.getAttribute('src')).toBe('https://image.url');
239
240
  });
240
241
 
242
+ it('should render placeholder when whatsappImageSrc is a template variable', () => {
243
+ const props = {
244
+ ...defaultProps,
245
+ content: {
246
+ templateMsg: 'Message',
247
+ whatsappImageSrc: '{{imageUrl}}',
248
+ },
249
+ };
250
+
251
+ const { container } = render(
252
+ <TestWrapper>
253
+ <ComponentToRender {...props} />
254
+ </TestWrapper>
255
+ );
256
+
257
+ expect(container.querySelector('.whatsapp-image-url-placeholder')).toBeTruthy();
258
+ expect(container.querySelector('.whatsapp-image-url-placeholder-icon')).toBeTruthy();
259
+ expect(container.querySelector('.whatsapp-image-url-placeholder-text').textContent).toBe('{{imageUrl}}');
260
+ expect(container.querySelector('img.whatsapp-image')).toBeFalsy();
261
+ });
262
+
263
+ it('should fall back to empty preview image when image fails to load', () => {
264
+ const props = {
265
+ ...defaultProps,
266
+ content: {
267
+ templateMsg: 'Message',
268
+ whatsappImageSrc: 'https://broken.url/image.jpg',
269
+ },
270
+ };
271
+
272
+ const { container } = render(
273
+ <TestWrapper>
274
+ <ComponentToRender {...props} />
275
+ </TestWrapper>
276
+ );
277
+
278
+ const image = container.querySelector('img.whatsapp-image');
279
+ expect(image).toBeTruthy();
280
+ expect(image.getAttribute('src')).toBe('https://broken.url/image.jpg');
281
+
282
+ // Trigger onError to swap src to fallback
283
+ const errorEvent = new Event('error');
284
+ image.dispatchEvent(errorEvent);
285
+
286
+ expect(image.getAttribute('src')).not.toBe('https://broken.url/image.jpg');
287
+ });
288
+
241
289
  it('should render video preview when whatsappVideoPreviewImg is present', () => {
242
290
  const props = {
243
291
  ...defaultProps,
@@ -816,6 +816,27 @@
816
816
  }
817
817
  }
818
818
 
819
+ .whatsapp-image-url-placeholder {
820
+ display: flex;
821
+ flex-direction: column;
822
+ align-items: center;
823
+ justify-content: center;
824
+ background-color: $CAP_G09;
825
+ border-radius: $CAP_SPACE_04;
826
+ width: 100%;
827
+ min-height: 5rem;
828
+ max-height: 8.786rem;
829
+ margin-bottom: $CAP_SPACE_04;
830
+ .whatsapp-image-url-placeholder-text {
831
+ font-size: 0.786rem;
832
+ color: $CAP_G04;
833
+ margin-top: $CAP_SPACE_04;
834
+ text-align: center;
835
+ word-break: break-all;
836
+ padding: 0 $CAP_SPACE_04;
837
+ }
838
+ }
839
+
819
840
  .msg-container-carousel {
820
841
  display: flex;
821
842
  flex-direction: column;
@@ -48,7 +48,7 @@ import { TEMPLATE, IMAGE_CAROUSEL, IMAGE, STICKER, TEXT, IMAGE_MAP, VIDEO } from
48
48
  import CapFacebookPreview from '../../v2Containers/CapFacebookPreview';
49
49
  import WhatsappStatusContainer from '../WhatsappStatusContainer';
50
50
  import { getWhatsappQuickReply, getWhatsappCarouselButtonView } from '../../v2Containers/Whatsapp/utils';
51
- import { QUICK_REPLY, WHATSAPP_CATEGORIES, PHONE_NUMBER } from '../../v2Containers/Whatsapp/constants';
51
+ import { QUICK_REPLY, WHATSAPP_CATEGORIES, PHONE_NUMBER, TEMPLATE_VARIABLE_REGEX } from '../../v2Containers/Whatsapp/constants';
52
52
  import { RCS_BUTTON_TYPES, LEFT, HORIZONTAL, VERTICAL, RIGHT} from '../../v2Containers/Rcs/constants';
53
53
  import { ANDROID, INAPP_MESSAGE_LAYOUT_TYPES } from '../../v2Containers/InApp/constants';
54
54
  import { CAROUSEL } from '../../v2Containers/MobilePushNew/constants';
@@ -66,6 +66,8 @@ const lineVideoPlaceholder = require('../../assets/rich-video-placeholder.svg');
66
66
  const androidPushMessagePhone = require('./assets/images/Android_With_date_and_time.svg');
67
67
  const iPhonePushMessagePhone = require('./assets/images/iOS_With_date_and_time.svg');
68
68
 
69
+ const { CapLabelInline } = CapLabel;
70
+
69
71
  export class TemplatePreview extends React.Component { // eslint-disable-line react/prefer-stateless-function
70
72
  onPreviewContentClicked = (channel) => {
71
73
  const IOSContent = (this.props.templateDataRaw && this.props.templateDataRaw.versions.base.IOS) ||
@@ -1194,11 +1196,21 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
1194
1196
  {content?.showUrlPreview
1195
1197
  && renderUrlPreview(content?.metaTagsDetails)}
1196
1198
  {content?.whatsappImageSrc && (
1197
- <CapImage
1198
- src={content.whatsappImageSrc}
1199
- className="whatsapp-image"
1200
- alt={formatMessage(messages.previewGenerated)}
1201
- />
1199
+ TEMPLATE_VARIABLE_REGEX.test(content.whatsappImageSrc) ? (
1200
+ <div className="whatsapp-image-url-placeholder">
1201
+ <CapIcon type="picture" size="s" className="whatsapp-image-url-placeholder-icon" />
1202
+ <CapLabelInline type="label2" className="whatsapp-image-url-placeholder-text">
1203
+ {content.whatsappImageSrc}
1204
+ </CapLabelInline>
1205
+ </div>
1206
+ ) : (
1207
+ <CapImage
1208
+ src={content.whatsappImageSrc}
1209
+ className="whatsapp-image"
1210
+ alt={formatMessage(messages.previewGenerated)}
1211
+ onError={(e) => { e.target.src = whatsappImageEmptyPreview; }}
1212
+ />
1213
+ )
1202
1214
  )}
1203
1215
  {content?.whatsappVideoPreviewImg && (
1204
1216
  <CapTooltip
@@ -43,6 +43,7 @@ exports[`Test Templates container Should render correct preview component for wh
43
43
  <CapImage
44
44
  alt="Preview is being generated"
45
45
  className="whatsapp-image"
46
+ onError={[Function]}
46
47
  src="https://crm-nightly-new-fileservice.s3.amazonaws.com/intouch_creative_assets/c9edc114-923b-4ac7-82d0-d6682213.jpg"
47
48
  />
48
49
  You have received {{1}} points
@@ -24,7 +24,6 @@ function SlideBoxFooter(props) {
24
24
  slidBoxContent,
25
25
  onSave,
26
26
  onEditTemplate,
27
- isTemplateArchived,
28
27
  onCreateNextStep,
29
28
  isFullMode,
30
29
  fetchingCmsData,
@@ -215,7 +214,7 @@ function SlideBoxFooter(props) {
215
214
  <FormattedMessage {...(continueButtonLabel || messages.continue)} />
216
215
  </CapButton>
217
216
  )}
218
- {slidBoxContent === PREVIEW && !isTemplateArchived && (
217
+ {slidBoxContent === PREVIEW && (
219
218
  <CapButton onClick={onEditTemplate} type="secondary">
220
219
  <FormattedMessage {...messages.creativesTemplatesEdit} />
221
220
  </CapButton>
@@ -228,7 +227,6 @@ SlideBoxFooter.propTypes = {
228
227
  slidBoxContent: PropTypes.node,
229
228
  onSave: PropTypes.func,
230
229
  onEditTemplate: PropTypes.func,
231
- isTemplateArchived: PropTypes.bool,
232
230
  onCreateNextStep: PropTypes.func,
233
231
  shouldShowContinueFooter: PropTypes.func,
234
232
  shouldShowDoneFooter: PropTypes.func,
@@ -50,7 +50,7 @@ import { RCS_STATUSES } from '../Rcs/constants';
50
50
  import { CREATIVE } from '../Facebook/constants';
51
51
  import { LOYALTY } from '../App/constants';
52
52
  import {
53
- WHATSAPP_STATUSES, WHATSAPP_MEDIA_TYPES, PHONE_NUMBER, URL,
53
+ WHATSAPP_STATUSES, WHATSAPP_MEDIA_TYPES, PHONE_NUMBER, URL, ADD_IMAGE_URL_PREVIEW_MARKER,
54
54
  } from '../Whatsapp/constants';
55
55
  import { updateImagesInHtml } from '../../utils/cdnTransformation';
56
56
 
@@ -260,10 +260,6 @@ export class Creatives extends React.Component {
260
260
  };
261
261
 
262
262
  onEditTemplate = () => {
263
- if (this.props.templateData && this.props.templateData.isArchived) {
264
- CapNotification.error({ message: this.props.intl.formatMessage(messages.cannotEditArchivedTemplate) });
265
- return;
266
- }
267
263
  this.setState({ slidBoxContent: 'editTemplate', showSlideBox: true, templateNameExists: true });
268
264
  };
269
265
 
@@ -688,6 +684,8 @@ export class Creatives extends React.Component {
688
684
  switch (mediaType) {
689
685
  case (WHATSAPP_MEDIA_TYPES.IMAGE):
690
686
  mediaParams.imageUrl = url;
687
+ // Detect isAddImageUrl from previewUrl marker (previewUrl is unused for IMAGE type)
688
+ mediaParams.isAddImageUrl = previewUrl === ADD_IMAGE_URL_PREVIEW_MARKER;
691
689
  break;
692
690
  case (WHATSAPP_MEDIA_TYPES.VIDEO):
693
691
  mediaParams.videoUrl = url;
@@ -1146,6 +1144,7 @@ export class Creatives extends React.Component {
1146
1144
  headerTemplate = '',
1147
1145
  } = {},
1148
1146
  isPreviewUrl = false,
1147
+ isAddImageUrl = false,
1149
1148
  carouselData = [],
1150
1149
  } = cloneDeep(versions.base.content.whatsapp);
1151
1150
 
@@ -1182,6 +1181,11 @@ export class Creatives extends React.Component {
1182
1181
  switch (mediaType) {
1183
1182
  case (WHATSAPP_MEDIA_TYPES.IMAGE):
1184
1183
  whatsappMedia.url = imageUrl;
1184
+ // previewUrl is unused for IMAGE type — reuse it to persist isAddImageUrl flag
1185
+ // without adding any new field that the backend would reject
1186
+ if (isAddImageUrl) {
1187
+ whatsappMedia.previewUrl = ADD_IMAGE_URL_PREVIEW_MARKER;
1188
+ }
1185
1189
  break;
1186
1190
  case (WHATSAPP_MEDIA_TYPES.VIDEO):
1187
1191
  whatsappMedia.url = videoUrl;
@@ -2140,7 +2144,6 @@ export class Creatives extends React.Component {
2140
2144
  onSave={this.saveMessage}
2141
2145
  onDiscard={this.discardMessage}
2142
2146
  onEditTemplate={this.onEditTemplate}
2143
- isTemplateArchived={!!(this.props.templateData && this.props.templateData.isArchived)}
2144
2147
  slidBoxContent={slidBoxContent}
2145
2148
  onCreateNextStep={this.onCreateNextStep}
2146
2149
  currentChannel={currentChannel.toUpperCase()}
@@ -390,8 +390,4 @@ export default defineMessages({
390
390
  id: `${scope}.personalizationTokensErrorMessage`,
391
391
  defaultMessage: `Personalization tags are not supported for anonymous customers, please remove the tags.`,
392
392
  },
393
- "cannotEditArchivedTemplate": {
394
- id: `${scope}.cannotEditArchivedTemplate`,
395
- defaultMessage: 'Cannot edit an archived template. Please unarchive it first.',
396
- },
397
393
  });
@@ -295,7 +295,6 @@ exports[`Test SlideBoxContent container campaign message, whatsapp edit all data
295
295
  isEmptyContent={false}
296
296
  isFullMode={false}
297
297
  isLiquidValidationError={false}
298
- isTemplateArchived={false}
299
298
  isTemplateNameEmpty={false}
300
299
  onCreateNextStep={[Function]}
301
300
  onDiscard={[Function]}
@@ -435,7 +434,6 @@ exports[`Test SlideBoxContent container campaign message, whatsapp edit min data
435
434
  isEmptyContent={false}
436
435
  isFullMode={false}
437
436
  isLiquidValidationError={false}
438
- isTemplateArchived={false}
439
437
  isTemplateNameEmpty={false}
440
438
  onCreateNextStep={[Function]}
441
439
  onDiscard={[Function]}
@@ -575,7 +573,6 @@ exports[`Test SlideBoxContent container it should clear the url, on channel chan
575
573
  isEmptyContent={false}
576
574
  isFullMode={false}
577
575
  isLiquidValidationError={false}
578
- isTemplateArchived={false}
579
576
  isTemplateNameEmpty={false}
580
577
  onCreateNextStep={[Function]}
581
578
  onDiscard={[Function]}