@capillarytech/creatives-library 9.0.15-alpha.1 → 9.0.15-alpha.3

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.
@@ -102,7 +102,7 @@ exports[`RCS utils getRCSContent renders RCS content with image and CTA suggesti
102
102
  style={Object {}}
103
103
  >
104
104
  <i
105
- className="cap-icon-v2 cap-icon-v2-launch xs"
105
+ className="cap-icon-v2 cap-icon-v2-open-in-new xs"
106
106
  onClick={[Function]}
107
107
  style={
108
108
  Object {
@@ -113,40 +113,12 @@ exports[`RCS utils getRCSContent renders RCS content with image and CTA suggesti
113
113
  <svg
114
114
  fill="currentColor"
115
115
  height="1em"
116
- viewBox="0 0 24 24"
116
+ viewBox="0 0 16 16"
117
117
  width="1em"
118
118
  >
119
- <defs>
120
- <path
121
- d="M19 19H5V5h5a1 1 0 0 0 0-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2v-6a1 1 0 0 0-2 0v6zM14 4a1 1 0 0 0 1 1h2.59l-6.297 6.297a.997.997 0 0 0 1.41 1.41L19 6.41V9a1 1 0 0 0 2 0V4a1 1 0 0 0-1-1h-5a1 1 0 0 0-1 1z"
122
- id="launch_svg__a"
123
- />
124
- </defs>
125
- <g
126
- fill="none"
127
- fillRule="evenodd"
128
- >
129
- <mask
130
- fill="currentColor"
131
- id="launch_svg__b"
132
- >
133
- <use
134
- xlinkHref="#launch_svg__a"
135
- />
136
- </mask>
137
- <use
138
- fill="currentColor"
139
- xlinkHref="#launch_svg__a"
140
- />
141
- <g
142
- fill="currentColor"
143
- mask="url(#launch_svg__b)"
144
- >
145
- <path
146
- d="M0 0h24v24H0z"
147
- />
148
- </g>
149
- </g>
119
+ <path
120
+ d="M12 12H4V4h2a1 1 0 1 0 0-2H4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8c1.1 0 2-.9 2-2v-2a1 1 0 0 0-2 0v2zM8.142 2.976c0 .54.437.977.977.977h1.552L7.285 7.338a.973.973 0 1 0 1.377 1.377l3.385-3.386v1.552a.976.976 0 1 0 1.953 0V3a1 1 0 0 0-1-1H9.119a.976.976 0 0 0-.977.976z"
121
+ />
150
122
  </svg>
151
123
  </i>
152
124
  Shop
@@ -256,7 +228,7 @@ exports[`RCS utils getRCSContent renders RCS content with multiple suggestion ty
256
228
  style={Object {}}
257
229
  >
258
230
  <i
259
- className="cap-icon-v2 cap-icon-v2-launch xs"
231
+ className="cap-icon-v2 cap-icon-v2-open-in-new xs"
260
232
  onClick={[Function]}
261
233
  style={
262
234
  Object {
@@ -267,40 +239,12 @@ exports[`RCS utils getRCSContent renders RCS content with multiple suggestion ty
267
239
  <svg
268
240
  fill="currentColor"
269
241
  height="1em"
270
- viewBox="0 0 24 24"
242
+ viewBox="0 0 16 16"
271
243
  width="1em"
272
244
  >
273
- <defs>
274
- <path
275
- d="M19 19H5V5h5a1 1 0 0 0 0-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2v-6a1 1 0 0 0-2 0v6zM14 4a1 1 0 0 0 1 1h2.59l-6.297 6.297a.997.997 0 0 0 1.41 1.41L19 6.41V9a1 1 0 0 0 2 0V4a1 1 0 0 0-1-1h-5a1 1 0 0 0-1 1z"
276
- id="launch_svg__a"
277
- />
278
- </defs>
279
- <g
280
- fill="none"
281
- fillRule="evenodd"
282
- >
283
- <mask
284
- fill="currentColor"
285
- id="launch_svg__b"
286
- >
287
- <use
288
- xlinkHref="#launch_svg__a"
289
- />
290
- </mask>
291
- <use
292
- fill="currentColor"
293
- xlinkHref="#launch_svg__a"
294
- />
295
- <g
296
- fill="currentColor"
297
- mask="url(#launch_svg__b)"
298
- >
299
- <path
300
- d="M0 0h24v24H0z"
301
- />
302
- </g>
303
- </g>
245
+ <path
246
+ d="M12 12H4V4h2a1 1 0 1 0 0-2H4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8c1.1 0 2-.9 2-2v-2a1 1 0 0 0-2 0v2zM8.142 2.976c0 .54.437.977.977.977h1.552L7.285 7.338a.973.973 0 1 0 1.377 1.377l3.385-3.386v1.552a.976.976 0 1 0 1.953 0V3a1 1 0 0 0-1-1H9.119a.976.976 0 0 0-.977.976z"
247
+ />
304
248
  </svg>
305
249
  </i>
306
250
  URL Action
@@ -207,13 +207,13 @@ describe('Creatives rcs test/>', () => {
207
207
  expect(options[2].value).toBe('CAROUSEL');
208
208
  expect(options[2].disabled).toBeFalsy();
209
209
 
210
- // Disabled state in edit mode
211
- renderHelper({
210
+ // Edit mode: radio group is replaced by a plain readonly value, not shown disabled
211
+ renderHelper({
212
212
  params,
213
213
  rcsData: { templateDetails: mockData.rcsRichCardTemplate }
214
214
  });
215
- const editModeRadioGroup = renderedComponent.find('CapRadioGroup#select-rcs-template-type').at(0);
216
- expect(editModeRadioGroup.props().disabled).toBe(true);
215
+ expect(renderedComponent.find('CapRadioGroup#select-rcs-template-type')).toHaveLength(0);
216
+ expect(renderedComponent.find('CapHeading.rcs-creative-name-value').length).toBeGreaterThan(0);
217
217
  expect(renderedComponent).toMatchSnapshot();
218
218
  });
219
219
 
@@ -116,7 +116,7 @@ describe('RCS utils', () => {
116
116
  expectGetRCSContentSnapshot({});
117
117
  });
118
118
 
119
- it('renders RCS carousel listing preview when cardType is carousel', () => {
119
+ it('renders RCS carousel listing preview when cardType is carousel — first card only, no peek', () => {
120
120
  const template = JSON.parse(JSON.stringify(mockData.editData1.templateDetails));
121
121
  const rcsContent = template.versions.base.content.RCS.rcsContent;
122
122
  rcsContent.cardType = 'CAROUSEL';
@@ -148,9 +148,10 @@ describe('RCS utils', () => {
148
148
  ];
149
149
 
150
150
  render(getRCSContent(template));
151
- // "Peek" shows max 3
152
- expect(document.querySelectorAll('.whatsapp-carousel-container').length).toBe(3);
153
- expect(document.querySelectorAll('.scroll-container').length).toBeGreaterThan(0);
151
+ // Listing preview only ever shows the first card — no peek/scroll of the rest (Figma).
152
+ expect(document.querySelectorAll('.whatsapp-carousel-container').length).toBe(1);
153
+ expect(document.querySelector('.whatsapp-carousel-body').textContent).toContain('Desc1');
154
+ expect(document.querySelector('.whatsapp-carousel-body').textContent).not.toContain('Desc2');
154
155
  });
155
156
 
156
157
  const carouselTemplate = (cardContent) => ({
@@ -214,7 +215,7 @@ describe('RCS utils', () => {
214
215
  expect(document.querySelector('.rcs-video-preview-label')?.textContent).toContain('Video preview');
215
216
  });
216
217
 
217
- it('carousel: uses whatsapp-message-without-media when card has no image/video URL', () => {
218
+ it('carousel: media box renders no image when card has no image/video URL', () => {
218
219
  const template = carouselTemplate([
219
220
  {
220
221
  title: 'Text only',
@@ -229,11 +230,12 @@ describe('RCS utils', () => {
229
230
  },
230
231
  ]);
231
232
  render(getRCSContent(template));
232
- const withoutMedia = document.querySelectorAll('.whatsapp-message-without-media');
233
- expect(withoutMedia.length).toBeGreaterThan(0);
233
+ const mediaBoxes = document.querySelectorAll('.rcs-carousel-listing-media-box');
234
+ expect(mediaBoxes.length).toBeGreaterThan(0);
235
+ expect(mediaBoxes[0].querySelector('.whatsapp-image:not(.rcs-video-preview-placeholder)')).toBeFalsy();
234
236
  });
235
237
 
236
- it('carousel: body includes description with newline when description is set', () => {
238
+ it('carousel: body shows description only no card title', () => {
237
239
  const template = carouselTemplate([
238
240
  {
239
241
  title: 'Head',
@@ -250,30 +252,26 @@ describe('RCS utils', () => {
250
252
  ]);
251
253
  render(getRCSContent(template));
252
254
  const bodies = document.querySelectorAll('.whatsapp-carousel-body');
253
- expect(bodies[0].textContent).toContain('Head');
255
+ expect(bodies[0].textContent).not.toContain('Head');
254
256
  expect(bodies[0].textContent).toContain('Tail line');
255
257
  });
256
258
 
257
- it('carousel: body handles missing title and empty description', () => {
259
+ it('carousel: body handles missing title and empty description (first card only)', () => {
258
260
  const template = carouselTemplate([
259
261
  {
262
+ title: 'Only title',
260
263
  description: '',
261
264
  media: { mediaUrl: 'https://example.com/x.png' },
262
265
  suggestions: [],
263
266
  },
264
- {
265
- title: 'Only title',
266
- media: { mediaUrl: 'https://example.com/y.png' },
267
- suggestions: [],
268
- },
269
267
  ]);
270
268
  render(getRCSContent(template));
271
- const bodies = document.querySelectorAll('.whatsapp-carousel-body');
272
- expect(bodies[0].textContent.trim()).toBe('');
273
- expect(bodies[1].textContent).toContain('Only title');
269
+ // Title is never rendered in the listing preview, so a title-only card is blank.
270
+ const body = document.querySelector('.whatsapp-carousel-body');
271
+ expect(body.textContent.trim()).toBe('');
274
272
  });
275
273
 
276
- it('carousel: renders per-card suggestions via renderRcsSuggestionsPreview', () => {
274
+ it('carousel: does not render buttons/suggestions in the listing preview', () => {
277
275
  const template = carouselTemplate([
278
276
  {
279
277
  title: 'C1',
@@ -289,8 +287,7 @@ describe('RCS utils', () => {
289
287
  },
290
288
  ]);
291
289
  render(getRCSContent(template));
292
- expect(document.querySelectorAll('.rcs-cta-preview').length).toBeGreaterThanOrEqual(1);
293
- expect(Array.from(document.querySelectorAll('.rcs-cta-preview')).some((el) => el.textContent.includes('Go'))).toBe(true);
290
+ expect(document.querySelectorAll('.rcs-cta-preview').length).toBe(0);
294
291
  });
295
292
 
296
293
  it('carousel: does not render suggestion block when suggestions missing or not an array', () => {
@@ -450,7 +450,7 @@ export const getRCSContent = (template) => {
450
450
  } else if (suggestion.type === RCS_BUTTON_TYPES.CTA) {
451
451
  renderArray.push(
452
452
  <CapLabel type="label21" className="rcs-cta-preview">
453
- <CapIcon type="launch" size="xs" />
453
+ <CapIcon type="open-in-new" size="xs" />
454
454
  {suggestion.text}
455
455
  </CapLabel>,
456
456
  );
@@ -493,58 +493,45 @@ export const getRCSContent = (template) => {
493
493
  const isTextOnlyCard = isRcsTextOnlyCardMediaType(mediaType);
494
494
  const mediaPreview = media?.thumbnailUrl ? media.thumbnailUrl : media.mediaUrl;
495
495
 
496
+ // Listing preview shows only the FIRST card — no peek of other cards, no buttons (Figma).
496
497
  const renderCarouselListingPreview = () => {
497
498
  const cards = Array.isArray(cardContent) ? cardContent : [];
498
499
  if (!cards.length) return null;
499
- const cardsToShow = cards.slice(0, 3); // enough to show a "peek" of next card
500
+ const c = cards[0];
501
+ const m = c?.media || {};
502
+ const isVideo = (c?.mediaType || m?.mediaType || '').toString().toUpperCase() === RCS_MEDIA_TYPES.VIDEO;
503
+ const thumbUrl = m?.thumbnailUrl;
504
+ const mediaUrl = m?.mediaUrl;
505
+ // Avoid rendering an <img src="...mp4"> when a video doesn't have a thumbnail.
506
+ const url = thumbUrl ? thumbUrl : (isVideo ? '' : mediaUrl);
500
507
  return (
501
- <div className="scroll-container">
502
- {cardsToShow.map((c, idx) => {
503
- const m = c?.media || {};
504
- const isVideo = (c?.mediaType || m?.mediaType || '').toString().toUpperCase() === RCS_MEDIA_TYPES.VIDEO;
505
- const thumbUrl = m?.thumbnailUrl;
506
- const mediaUrl = m?.mediaUrl;
507
- // Avoid rendering an <img src="...mp4"> when a video doesn't have a thumbnail.
508
- const url = thumbUrl ? thumbUrl : (isVideo ? '' : mediaUrl);
509
- return (
510
- <div
511
- key={`rcs-carousel-listing-${idx}-${url || ''}`}
512
- className="whatsapp-carousel-container"
513
- role="group"
514
- >
515
- <div className="whatsapp-carousel-card">
516
- {url && (
517
- <CapImage
518
- src={url}
519
- className="whatsapp-image"
520
- />
521
- )}
522
- {!url && isVideo && (
523
- <div
524
- className="whatsapp-image video-preview rcs-video-preview-placeholder"
525
- >
526
- <CapLabel type="label9" className="rcs-video-preview-label">
527
- Video preview
528
- </CapLabel>
529
- </div>
530
- )}
531
- <span
532
- className={`${url ? 'whatsapp-message-with-media' : 'whatsapp-message-without-media'}`}
533
- >
534
- <CapLabel type="label9" className="whatsapp-carousel-body">
535
- {(url || isVideo) ? (c?.title || '') : ''}
536
- {c?.description ? `\n${c?.description}` : ''}
537
- </CapLabel>
538
- </span>
539
- {Array.isArray(c?.suggestions) && c.suggestions.length > 0 && (
540
- <>
541
- {renderRcsSuggestionsPreview(c.suggestions)}
542
- </>
543
- )}
508
+ <div className="whatsapp-carousel-container" role="group">
509
+ <div className="whatsapp-carousel-card">
510
+ {/* Text and media render as two separate rounded cards, not one merged
511
+ box (Figma listing reference) description only, no card title. */}
512
+ <div className="rcs-carousel-listing-text-box">
513
+ <CapLabel type="label9" className="whatsapp-carousel-body">
514
+ {c?.description || ''}
515
+ </CapLabel>
516
+ </div>
517
+ <div className="rcs-carousel-listing-media-box">
518
+ {url && (
519
+ <CapImage
520
+ src={url}
521
+ className="whatsapp-image"
522
+ />
523
+ )}
524
+ {!url && isVideo && (
525
+ <div
526
+ className="whatsapp-image video-preview rcs-video-preview-placeholder"
527
+ >
528
+ <CapLabel type="label9" className="rcs-video-preview-label">
529
+ Video preview
530
+ </CapLabel>
544
531
  </div>
545
- </div>
546
- );
547
- })}
532
+ )}
533
+ </div>
534
+ </div>
548
535
  </div>
549
536
  );
550
537
  };
@@ -279,36 +279,61 @@
279
279
  margin: $CAP_SPACE_12 0;
280
280
  }
281
281
  }
282
- .scroll-container {
283
- overflow-x: auto;
284
- overflow-y: hidden;
285
- display: flex;
286
- flex-wrap: nowrap;
282
+ // Listing preview shows only the first card — no scroll/peek (Figma).
283
+ .whatsapp-carousel-container {
284
+ padding: $CAP_SPACE_04;
287
285
  width: 100%;
288
- padding-top: $CAP_SPACE_06;
289
- padding-right: $CAP_SPACE_06;
290
- white-space: nowrap;
291
- scrollbar-width: none; // Hide scrollbar in Firefox
292
- &::-webkit-scrollbar {
293
- display: none; // Hide scrollbar in Chrome/Safari/Opera
286
+ box-sizing: border-box;
287
+ white-space: pre-wrap;
288
+ word-break: break-word;
289
+ overflow: auto;
290
+ text-align: left;
291
+ .whatsapp-carousel-card {
292
+ display: flex;
293
+ flex-direction: column;
294
+ gap: $CAP_SPACE_04;
294
295
  }
295
- .whatsapp-carousel-container {
296
- padding: $CAP_SPACE_04 0 $CAP_SPACE_08;
297
- border-radius: $CAP_SPACE_06;
296
+ // Text and media are two separate rounded cards, not one merged
297
+ // box (Figma listing reference) — text first, then media below it.
298
+ // Borderless + shadow-only elevation, matching the established
299
+ // convention for preview carousel cards (see .mobile-push-carousel-card
300
+ // in v2Components/TemplatePreview/_templatePreview.scss) — a visible
301
+ // border read as "boxed in" against the $CAP_G09 card background.
302
+ .rcs-carousel-listing-text-box {
298
303
  background-color: $CAP_WHITE;
299
- width: 80%;
300
- flex-shrink: 0;
301
- margin-right: $CAP_SPACE_04;
302
- white-space: pre-wrap;
303
- word-break: break-word;
304
- overflow: auto;
305
- text-align: left;
306
- .whatsapp-carousel-card {
307
- margin: $CAP_SPACE_02 $CAP_SPACE_06 $CAP_SPACE_01 $CAP_SPACE_08;
308
- .whatsapp-carousel-body {
309
- margin-bottom: $CAP_SPACE_08;
310
- white-space: pre-wrap;
311
- }
304
+ border-radius: $CAP_SPACE_04;
305
+ padding: $CAP_SPACE_12;
306
+ box-shadow: 0 0 0.625rem 0 rgba(0, 0, 0, 0.08);
307
+ .whatsapp-carousel-body {
308
+ white-space: pre-wrap;
309
+ overflow: hidden;
310
+ text-overflow: ellipsis;
311
+ display: -webkit-box;
312
+ -webkit-line-clamp: 2;
313
+ line-clamp: 2;
314
+ -webkit-box-orient: vertical;
315
+ }
316
+ }
317
+ .rcs-carousel-listing-media-box {
318
+ background-color: $CAP_WHITE;
319
+ border-radius: $CAP_SPACE_08;
320
+ padding: $CAP_SPACE_04;
321
+ box-sizing: border-box;
322
+ box-shadow: 0 0 0.625rem 0 rgba(0, 0, 0, 0.08);
323
+ .whatsapp-image,
324
+ .rcs-video-preview-placeholder {
325
+ display: block;
326
+ width: 100%;
327
+ // Neutralize the global .whatsapp-image { max-height: 123px;
328
+ // margin-bottom: 4px; } rule (_templates.scss line ~1202) — that
329
+ // margin was leaving a background sliver inside this rounded box,
330
+ // making the radius clip look uneven between top and bottom.
331
+ height: 11rem;
332
+ max-height: none;
333
+ margin: 0;
334
+ object-fit: cover;
335
+ overflow: hidden;
336
+ border-radius: $CAP_SPACE_04;
312
337
  }
313
338
  }
314
339
  }
@@ -2572,9 +2572,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
2572
2572
  const name = get(template, "name", "");
2573
2573
  const statusDisplay=getRcsStatusType(status);
2574
2574
  templateData.title = (
2575
- <CapRow type='flex' align="middle" justify="start" gap={12}>
2576
- {isCardArchiveEligible && this.renderCardSelectionCheckbox({ templateId: template._id, selectedIds: selectedIdsArrayForCard, isDisabled: isAnyArchiveInProgress })}
2577
- <CapLabel className="whatsapp-rcs-template-name">{name}</CapLabel>
2575
+ <CapColumn>
2576
+ <CapRow type='flex' align="middle" justify="start" gap={12}>
2577
+ {isCardArchiveEligible && this.renderCardSelectionCheckbox({ templateId: template._id, selectedIds: selectedIdsArrayForCard, isDisabled: isAnyArchiveInProgress })}
2578
+ <CapLabel className="whatsapp-rcs-template-name">{name}</CapLabel>
2579
+ </CapRow>
2578
2580
  {this.state.hostName !== HOST_INFOBIP && <CapColumn type="flex" align="middle" className="rcs-status-container zalo-status-color">
2579
2581
  <CapStatus
2580
2582
  type={statusDisplay}
@@ -2583,7 +2585,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
2583
2585
  />
2584
2586
  </CapColumn>
2585
2587
  }
2586
- </CapRow>
2588
+ </CapColumn>
2587
2589
  );
2588
2590
 
2589
2591
  templateData.content = getRCSContent(template);