@capillarytech/creatives-library 8.0.353-alpha.1 → 8.0.353-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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.353-alpha.1",
4
+ "version": "8.0.353-alpha.3",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -29,6 +29,9 @@ import videoPlay from '../../../assets/videoPlay.svg';
29
29
  const smsMobileAndroid = require('../../../assets/Android.png');
30
30
  const smsMobileIos = require('../../../assets/iOS.png');
31
31
 
32
+ const getTrimmedText = (value = '') => (value ?? '').trim();
33
+ const hasTrimmedText = (value = '') => Boolean(getTrimmedText(value));
34
+
32
35
  const ViberPreviewContent = ({
33
36
  content,
34
37
  device,
@@ -58,10 +61,10 @@ const ViberPreviewContent = ({
58
61
 
59
62
  const cardHasMeaningfulContent = (card) => {
60
63
  if (!card || typeof card !== 'object') return false;
61
- if ((card.text || '').trim()) return true;
62
- if ((card.mediaUrl || '').trim()) return true;
63
- const buttons = card.buttons || [];
64
- return buttons.some((button) => (button?.title || '').trim());
64
+ if (hasTrimmedText(card?.text)) return true;
65
+ if (hasTrimmedText(card?.mediaUrl)) return true;
66
+ const buttons = card?.buttons ?? [];
67
+ return buttons.some((button) => hasTrimmedText(button?.title));
65
68
  };
66
69
 
67
70
  const hasMeaningfulCarousel =
@@ -76,8 +79,8 @@ const ViberPreviewContent = ({
76
79
  const previewCarouselCards =
77
80
  hasCarouselContent && Array.isArray(cards) && cards.length ? cards : hasCarouselContent ? [{}] : [];
78
81
 
79
- const trimmedMessageContent = (messageContent || '').trim();
80
- const trimmedButtonText = (buttonText || '').trim();
82
+ const trimmedMessageContent = getTrimmedText(messageContent);
83
+ const trimmedButtonText = getTrimmedText(buttonText);
81
84
 
82
85
  // Get account name (first letter for icon)
83
86
  const accountIcon = (accountName || brandName || 'V')[0]?.toUpperCase();
@@ -136,7 +139,7 @@ const ViberPreviewContent = ({
136
139
  // Check if there's any content to display (whitespace-only strings do not count)
137
140
  const hasContent = Boolean(
138
141
  trimmedMessageContent
139
- || (imageURL || '').trim()
142
+ || hasTrimmedText(imageURL)
140
143
  || videoParams?.viberVideoSrc
141
144
  || trimmedButtonText
142
145
  || hasMeaningfulCarousel
@@ -168,7 +171,7 @@ const ViberPreviewContent = ({
168
171
  {hasContent && (
169
172
  <CapRow className={`viber-message-container ${device !== ANDROID ? 'viber-message-container-ios' : ''}`}>
170
173
  {/* Brand Name Display (from TemplatePreview line 1136) */}
171
- <CapRow className="msg-container viber-preview">
174
+ <CapRow className={`msg-container viber-preview ${showCarouselInPreview ? 'viber-preview-carousel' : ''}`}>
172
175
  {/* Account Icon (from TemplatePreview line 1146-1160) */}
173
176
  {!hasCarouselContent && (
174
177
  <CapRow className="viber-account-icon">
@@ -177,14 +180,14 @@ const ViberPreviewContent = ({
177
180
  )}
178
181
 
179
182
  {/* Message Bubble (from TemplatePreview line 1161-1223) */}
180
- <CapRow className="message-pop align-left viber-message-pop">
183
+ <CapRow className={`message-pop align-left viber-message-pop ${showCarouselInPreview ? 'viber-message-pop-carousel' : ''}`}>
181
184
  {/* Text Viber preview */}
182
185
  {trimmedMessageContent && !hasCarouselContent && (
183
186
  <CapLabel type="label15" className="viber-message-text">{messageContent}</CapLabel>
184
187
  )}
185
188
 
186
189
  {/* Image Viber preview */}
187
- {(imageURL || '').trim() && (
190
+ {hasTrimmedText(imageURL) && (
188
191
  <CapImage
189
192
  src={imageURL}
190
193
  className="viber-image-preview"
@@ -224,52 +227,68 @@ const ViberPreviewContent = ({
224
227
  {/* Carousel Viber preview */}
225
228
  {showCarouselInPreview && (
226
229
  <>
227
- <CapRow className="viber-carousel-message-box">
230
+ <CapRow className="viber-carousel-message-pop">
228
231
  {trimmedMessageContent ? (
229
- <CapLabel type="label15" className="viber-carousel-message-box-text">
232
+ <CapLabel
233
+ type="label15"
234
+ className="message-pop-item align-left viber-message-text viber-carousel-message-box-text"
235
+ >
230
236
  {messageContent}
231
237
  </CapLabel>
232
238
  ) : (
233
239
  <CapRow className="viber-carousel-message-box-placeholder" />
234
240
  )}
241
+ <CapLabel type="label1" className="viber-carousel-message-timestamp">
242
+ {timestamp}
243
+ </CapLabel>
235
244
  </CapRow>
236
- <CapRow className="viber-carousel-preview-scroll">
237
- {previewCarouselCards?.map((card, index) => (
238
- <CapRow className="viber-carousel-preview-card" key={`viber-carousel-preview-card-${index}`}>
239
- {(card?.mediaUrl || '').trim() ? (
240
- <CapImage
241
- src={card?.mediaUrl}
242
- className="viber-carousel-preview-image"
243
- alt="Viber carousel card"
244
- />
245
- ) : (
246
- <CapRow className="viber-carousel-preview-image-placeholder" />
247
- )}
248
- {(card?.text || '').trim() ? (
249
- <CapLabel type="label15" className="viber-carousel-preview-text">
250
- {card?.text}
251
- </CapLabel>
252
- ) : (
253
- <CapLabel type="label15" className="viber-carousel-preview-text-placeholder" />
254
- )}
255
- {(card?.buttons?.filter((cardButton) => (cardButton?.title || '').trim()) ?? [])
256
- .slice(0, 2)
257
- .map((cardButton, btnIndex) => (
258
- <CapLabel
259
- className={`viber-carousel-preview-button ${btnIndex === 1 ? 'viber-carousel-preview-button-secondary' : ''}`}
260
- key={`viber-carousel-preview-btn-${index}-${btnIndex}-${(cardButton?.title || '').trim()}`}
261
- >
262
- {(cardButton?.title || '').trim()}
263
- </CapLabel>
264
- ))}
265
- </CapRow>
266
- ))}
245
+
246
+ <CapRow className="viber-carousel-cards-pop">
247
+ <CapRow className="viber-carousel-preview-scroll">
248
+ {previewCarouselCards?.map((card, index) => (
249
+ <CapRow className="viber-carousel-preview-card" key={`viber-carousel-preview-card-${index}`}>
250
+ {hasTrimmedText(card?.mediaUrl) ? (
251
+ <CapImage
252
+ src={card?.mediaUrl}
253
+ className="viber-carousel-preview-image"
254
+ alt="Viber carousel card"
255
+ />
256
+ ) : (
257
+ <CapRow className="viber-carousel-preview-image-placeholder" />
258
+ )}
259
+ <CapRow className="viber-carousel-preview-card-body">
260
+ {hasTrimmedText(card?.text) ? (
261
+ <CapLabel type="label15" className="viber-carousel-preview-text">
262
+ {card?.text}
263
+ </CapLabel>
264
+ ) : (
265
+ <CapLabel type="label15" className="viber-carousel-preview-text-placeholder" />
266
+ )}
267
+ {(card?.buttons?.filter((cardButton) => hasTrimmedText(cardButton?.title)) ?? [])
268
+ .slice(0, 2)
269
+ .map((cardButton, btnIndex) => {
270
+ const trimmedCardButtonTitle = getTrimmedText(cardButton?.title);
271
+ return (
272
+ <CapLabel
273
+ className={`viber-carousel-preview-button ${btnIndex === 1 ? 'viber-carousel-preview-button-secondary' : ''}`}
274
+ key={`viber-carousel-preview-btn-${index}-${btnIndex}-${trimmedCardButtonTitle}`}
275
+ >
276
+ {trimmedCardButtonTitle}
277
+ </CapLabel>
278
+ );
279
+ })}
280
+ </CapRow>
281
+ </CapRow>
282
+ ))}
283
+ </CapRow>
267
284
  </CapRow>
268
285
  </>
269
286
  )}
270
- <CapLabel type="label1" className="viber-timestamp">
271
- {timestamp}
272
- </CapLabel>
287
+ {!showCarouselInPreview && (
288
+ <CapLabel type="label1" className="viber-timestamp">
289
+ {timestamp}
290
+ </CapLabel>
291
+ )}
273
292
  <CapRow className="empty-placeholder" />
274
293
  </CapRow>
275
294
 
@@ -2116,6 +2116,8 @@
2116
2116
  flex: 1;
2117
2117
  display: flex;
2118
2118
  flex-direction: column;
2119
+ overflow-y: auto;
2120
+ -webkit-overflow-scrolling: touch;
2119
2121
  padding: 0 $CAP_SPACE_16;
2120
2122
  background-color: #ffffff;
2121
2123
  margin-left: $CAP_SPACE_06;
@@ -2141,6 +2143,12 @@
2141
2143
  margin-top: $CAP_SPACE_16;
2142
2144
  width: 68%;
2143
2145
 
2146
+ &.viber-preview-carousel {
2147
+ width: 100%;
2148
+ margin-left: $CAP_SPACE_12;
2149
+ max-height: none;
2150
+ }
2151
+
2144
2152
  // Account icon (from TemplatePreview line 1146-1160)
2145
2153
  .viber-account-icon {
2146
2154
  width: $CAP_SPACE_20;
@@ -2169,6 +2177,18 @@
2169
2177
  border-radius: $CAP_SPACE_04;
2170
2178
  padding: $CAP_SPACE_04;
2171
2179
 
2180
+ &.viber-message-pop-carousel {
2181
+ width: 100%;
2182
+ left: 0;
2183
+ margin-top: 0;
2184
+ padding: 0;
2185
+ background: transparent;
2186
+ display: flex;
2187
+ flex-direction: column;
2188
+ align-items: flex-start;
2189
+ gap: $CAP_SPACE_06;
2190
+ }
2191
+
2172
2192
  // Text Viber preview (from TemplatePreview line 1166-1174)
2173
2193
  .viber-message-text {
2174
2194
  margin: 0.107rem $CAP_SPACE_06 $CAP_SPACE_01 0.5rem;
@@ -2240,38 +2260,110 @@
2240
2260
  }
2241
2261
  }
2242
2262
 
2263
+ .viber-carousel-message-pop,
2264
+ .viber-carousel-cards-pop {
2265
+ width: 100%;
2266
+ background: $CAP_G08;
2267
+ border-radius: $CAP_SPACE_06;
2268
+ padding: $CAP_SPACE_08;
2269
+ }
2270
+
2271
+ .viber-carousel-message-pop {
2272
+ margin-top: 0;
2273
+ width: 68%;
2274
+ border-radius: 0 $CAP_SPACE_06 $CAP_SPACE_06 $CAP_SPACE_06;
2275
+ }
2276
+
2277
+ .viber-carousel-cards-pop {
2278
+ margin-top: 0;
2279
+ width: 100%;
2280
+ background: transparent;
2281
+ border: none;
2282
+ border-radius: 0;
2283
+ padding: 0;
2284
+ }
2285
+
2286
+ .viber-carousel-message-box {
2287
+ width: 100%;
2288
+ min-height: 2.25rem;
2289
+ height: auto;
2290
+ border-radius: $CAP_SPACE_04;
2291
+ background: transparent;
2292
+ padding: 0 $CAP_SPACE_08;
2293
+ display: flex;
2294
+ align-items: center;
2295
+ }
2296
+
2297
+ .viber-carousel-message-box-text {
2298
+ color: $CAP_G01;
2299
+ margin: 0.107rem $CAP_SPACE_06 $CAP_SPACE_01 0.5rem;
2300
+ white-space: normal;
2301
+ word-break: break-word;
2302
+ overflow: visible;
2303
+ width: 100%;
2304
+ }
2305
+
2306
+ .viber-carousel-message-box-placeholder {
2307
+ width: 100%;
2308
+ height: 0.875rem;
2309
+ border-radius: $CAP_SPACE_04;
2310
+ background: $CAP_G07;
2311
+ }
2312
+
2313
+ .viber-carousel-message-timestamp,
2314
+ .viber-carousel-cards-timestamp {
2315
+ display: block;
2316
+ text-align: right;
2317
+ margin-top: $CAP_SPACE_06;
2318
+ color: $CAP_G04;
2319
+ }
2320
+
2243
2321
  .viber-carousel-preview-scroll {
2244
- margin-top: $CAP_SPACE_08;
2245
2322
  display: flex;
2323
+ width: 100%;
2246
2324
  overflow-x: auto;
2247
- gap: $CAP_SPACE_08;
2248
- white-space: nowrap;
2325
+ overflow-y: visible;
2326
+
2249
2327
  scrollbar-width: none;
2328
+ -webkit-overflow-scrolling: touch;
2250
2329
 
2251
2330
  &::-webkit-scrollbar {
2252
2331
  display: none;
2253
2332
  }
2254
2333
 
2255
2334
  .viber-carousel-preview-card {
2256
- min-width: 8.75rem;
2257
- background: $CAP_WHITE;
2258
- border-radius: $CAP_SPACE_04;
2259
- padding: $CAP_SPACE_06;
2335
+ flex: 0 0 68%;
2336
+ min-width: 68%;
2337
+ margin-right: $CAP_SPACE_08;
2338
+ background: $CAP_G09;
2339
+ border: 1px solid $CAP_G07;
2340
+ border-radius: $CAP_SPACE_12;
2341
+ overflow: hidden;
2260
2342
  display: flex;
2261
2343
  flex-direction: column;
2262
- gap: $CAP_SPACE_04;
2344
+
2345
+ &:last-child {
2346
+ margin-right: 0;
2347
+ }
2348
+
2349
+ .viber-carousel-preview-card-body {
2350
+ padding: $CAP_SPACE_08;
2351
+ display: flex;
2352
+ flex-direction: column;
2353
+ gap: $CAP_SPACE_06;
2354
+ }
2263
2355
 
2264
2356
  .viber-carousel-preview-image {
2265
2357
  width: 100%;
2266
- height: 5.357rem;
2358
+ height: 10rem;
2267
2359
  object-fit: cover;
2268
- border-radius: $CAP_SPACE_04;
2360
+ border-radius: 0;
2269
2361
  }
2270
2362
 
2271
2363
  .viber-carousel-preview-image-placeholder {
2272
2364
  width: 100%;
2273
- height: 5.357rem;
2274
- border-radius: $CAP_SPACE_04;
2365
+ height: 10rem;
2366
+ border-radius: 0;
2275
2367
  background: $CAP_G07;
2276
2368
  }
2277
2369
 
@@ -2294,45 +2386,23 @@
2294
2386
  background: $CAP_PURPLE01;
2295
2387
  border-radius: $CAP_SPACE_12;
2296
2388
  text-align: center;
2297
- padding: 0.179rem $CAP_SPACE_08;
2389
+ width: 100%;
2390
+ display: flex;
2391
+ align-items: center;
2392
+ justify-content: center;
2393
+ min-height: 1.5rem;
2394
+ padding: $CAP_SPACE_06 $CAP_SPACE_08;
2298
2395
  white-space: normal;
2299
- min-height: 1rem;
2300
2396
  }
2301
2397
 
2302
2398
  .viber-carousel-preview-button-secondary {
2303
2399
  color: $CAP_PURPLE01;
2304
- background: $CAP_WHITE;
2305
- border: 1px solid $CAP_PURPLE01;
2400
+ background: transparent;
2401
+ border: none;
2306
2402
  }
2307
2403
  }
2308
2404
  }
2309
2405
 
2310
- .viber-carousel-message-box {
2311
- width: 100%;
2312
- height: 2.25rem;
2313
- border-radius: $CAP_SPACE_04;
2314
- background: $CAP_WHITE;
2315
- margin-top: $CAP_SPACE_08;
2316
- padding: 0 $CAP_SPACE_08;
2317
- display: flex;
2318
- align-items: center;
2319
- }
2320
-
2321
- .viber-carousel-message-box-text {
2322
- color: $CAP_G01;
2323
- white-space: nowrap;
2324
- overflow: hidden;
2325
- text-overflow: ellipsis;
2326
- width: 100%;
2327
- }
2328
-
2329
- .viber-carousel-message-box-placeholder {
2330
- width: 100%;
2331
- height: 0.875rem;
2332
- border-radius: $CAP_SPACE_04;
2333
- background: $CAP_G07;
2334
- }
2335
-
2336
2406
  .empty-placeholder {
2337
2407
  height: $CAP_SPACE_08;
2338
2408
  }
@@ -215,7 +215,7 @@
215
215
  }
216
216
  .viber-carousel-static-button {
217
217
  display: block;
218
- min-height: 1rem;
218
+ min-height: 1.5rem;
219
219
  border-radius: $CAP_SPACE_12;
220
220
  background: $CAP_PURPLE01;
221
221
  color: $CAP_WHITE;
@@ -225,8 +225,9 @@
225
225
  }
226
226
  .viber-carousel-static-button-secondary {
227
227
  color: $CAP_PURPLE01;
228
- background: $CAP_WHITE;
229
- border: 1px solid $CAP_PURPLE01;
228
+ background: transparent;
229
+ border: none;
230
+ box-shadow: none;
230
231
  }
231
232
  }
232
233
 
@@ -1413,8 +1413,8 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1413
1413
  // Return Viber content object (same as Viber/index.js getTemplateContent)
1414
1414
  return {
1415
1415
  viberPreviewContent: viberPreview,
1416
- accountName: accountName ? [accountName] : [],
1417
- brandName: accountName ? [accountName] : [],
1416
+ accountName: accountName || '',
1417
+ brandName: accountName || '',
1418
1418
  messageContent: text,
1419
1419
  ...(isCarousel && {
1420
1420
  type: VIBER_MEDIA_TYPES.CAROUSEL,
@@ -96,6 +96,7 @@ const createEmptyCarouselButton = () => ({
96
96
  urlType: STATIC_URL,
97
97
  isSaved: false,
98
98
  hasAttemptedSave: false,
99
+ hasTouchedAction: false,
99
100
  });
100
101
  const createEmptyCarouselCard = () => ({
101
102
  id: getNextCarouselCardId(),
@@ -217,6 +218,7 @@ export const Viber = (props) => {
217
218
  urlType: carouselButton?.urlType || STATIC_URL,
218
219
  isSaved: Boolean(carouselButton?.title && carouselButton?.action),
219
220
  hasAttemptedSave: Boolean(carouselButton?.hasAttemptedSave),
221
+ hasTouchedAction: Boolean(carouselButton?.hasTouchedAction),
220
222
  })),
221
223
  }));
222
224
  const cardsToSet = normalizedCards.length
@@ -435,7 +437,7 @@ export const Viber = (props) => {
435
437
  if (!trimmedCardText) {
436
438
  return false;
437
439
  }
438
- if (trimmedCardText.length < VIBER_CAROUSEL_CARD_TITLE_MIN_LENGTH) {
440
+ if (trimmedCardText?.length < VIBER_CAROUSEL_CARD_TITLE_MIN_LENGTH) {
439
441
  return formatMessage(messages.carouselCardTitleMinLengthError);
440
442
  }
441
443
  if (cardText.length > VIBER_CAROUSEL_CARD_TITLE_MAX_LENGTH) {
@@ -445,11 +447,12 @@ export const Viber = (props) => {
445
447
  };
446
448
  const getCarouselButtonTitleError = (button = {}, buttonIndex = 0, showEmptyError = true) => {
447
449
  const title = button?.title || '';
450
+ const trimmedTitle = title.trim();
448
451
  const maxLength = getCarouselButtonTitleMaxLength(buttonIndex);
449
- if (!title.trim() && showEmptyError) {
452
+ if (!trimmedTitle && showEmptyError) {
450
453
  return formatMessage(messages.textCannotBeEmptyError);
451
454
  }
452
- if (!title.trim()) {
455
+ if (!trimmedTitle) {
453
456
  return false;
454
457
  }
455
458
  if (title.length > maxLength) {
@@ -459,11 +462,14 @@ export const Viber = (props) => {
459
462
  }
460
463
  return false;
461
464
  };
462
- const getCarouselButtonActionError = (button = {}) => {
465
+ const getCarouselButtonActionError = (button = {}, showEmptyError = true) => {
463
466
  const action = button?.action || '';
464
- if (!action.trim()) {
467
+ if (!action?.trim() && showEmptyError) {
465
468
  return formatMessage(messages.urlCannotBeEmptyError);
466
469
  }
470
+ if (!action?.trim()) {
471
+ return false;
472
+ }
467
473
  if (action.length > VIBER_CAROUSEL_BUTTON_URL_MAX_LENGTH) {
468
474
  return formatMessage(messages.carouselButtonUrlMaxLengthError);
469
475
  }
@@ -673,7 +679,28 @@ export const Viber = (props) => {
673
679
  return {
674
680
  ...card,
675
681
  buttons: (card?.buttons || []).map((button, idx) => (
676
- idx === buttonIndex ? { ...button, [key]: value, isSaved: false } : button
682
+ idx === buttonIndex
683
+ ? {
684
+ ...button,
685
+ [key]: value,
686
+ isSaved: false,
687
+ ...(key === 'action' && value?.trim() ? { hasTouchedAction: true } : {}),
688
+ }
689
+ : button
690
+ )),
691
+ };
692
+ }));
693
+ };
694
+
695
+ const markCarouselButtonActionTouched = (cardIndex, buttonIndex) => {
696
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
697
+ if (index !== cardIndex) {
698
+ return card;
699
+ }
700
+ return {
701
+ ...card,
702
+ buttons: (card?.buttons || []).map((button, idx) => (
703
+ idx === buttonIndex ? { ...button, hasTouchedAction: true } : button
677
704
  )),
678
705
  };
679
706
  }));
@@ -800,13 +827,15 @@ export const Viber = (props) => {
800
827
  updateOnReUpload={() => updateOnCarouselImageReUpload(cardIndex)}
801
828
  index={cardIndex}
802
829
  className="cap-custom-image-upload"
803
- key={`viber-carousel-image-upload-${card.id}`}
830
+ key={`viber-carousel-image-upload-${card?.id}`}
804
831
  imageData={viber}
805
832
  channel={VIBER}
806
833
  />
807
- <CapLabel type="label3" className="viber-carousel-image-recommendation">
808
- Supported image types are .jpg, .jpeg, .png. Max size: 10 MB. Recommended resolution: 696 px x 600 px.
809
- </CapLabel>
834
+ {showCarouselValidationErrors && !isUrl(card?.mediaUrl || '') && (
835
+ <CapLabel type="label3" className="viber-carousel-image-recommendation">
836
+ Supported image types are .jpg, .jpeg, .png. Max size: 10 MB. Recommended resolution: 696 px x 600 px.
837
+ </CapLabel>
838
+ )}
810
839
  <CapRow className="viber-carousel-card-title-header" type="flex" justify="space-between">
811
840
  <CapHeading type="h5">
812
841
  {formatMessage(messages.carouselCardTextLabel)}
@@ -839,13 +868,13 @@ export const Viber = (props) => {
839
868
  </CapHeading>
840
869
  {(card?.buttons || []).map((button, buttonIndex) => (
841
870
  // eslint-disable-next-line react/no-array-index-key
842
- <div className="viber-carousel-button" key={`carousel-button-${card.id}-${buttonIndex}`}>
843
- {button.isSaved ? (
871
+ <div className="viber-carousel-button" key={`carousel-button-${card?.id}-${buttonIndex}`}>
872
+ {button?.isSaved ? (
844
873
  <CapRow className="viber-carousel-saved-button" align="middle" type="flex">
845
874
  <CapIcon size="s" type="six-dots" className="viber-carousel-saved-button-icon" />
846
875
  <CapIcon size="s" type="reply" className="viber-carousel-saved-button-icon" />
847
876
  <CapLabel type="label4" className="viber-carousel-saved-button-text">
848
- {button.title}
877
+ {button?.title}
849
878
  </CapLabel>
850
879
  <CapColumn className="button-edit-icon" onClick={() => editCarouselButton(cardIndex, buttonIndex)}>
851
880
  <CapIcon type="edit" size="s" />
@@ -873,7 +902,7 @@ export const Viber = (props) => {
873
902
  </CapHeading>
874
903
  </CapRow>
875
904
  <CapInput
876
- value={button.title}
905
+ value={button?.title ?? ''}
877
906
  onChange={({ target: { value } }) => onCarouselButtonChange(cardIndex, buttonIndex, 'title', value)}
878
907
  placeholder={formatMessage(messages.carouselButtonTitlePlaceholder)}
879
908
  errorMessage={getCarouselButtonTitleError(
@@ -900,10 +929,16 @@ export const Viber = (props) => {
900
929
  <CapColumn span={18}>
901
930
  <CapInput
902
931
  label={formatMessage(messages.carouselButtonActionLabel)}
903
- value={button.action}
932
+ value={button?.action ?? ''}
904
933
  onChange={({ target: { value } }) => onCarouselButtonChange(cardIndex, buttonIndex, 'action', value)}
934
+ onBlur={() => markCarouselButtonActionTouched(cardIndex, buttonIndex)}
905
935
  placeholder={formatMessage(messages.carouselButtonActionPlaceholder)}
906
- errorMessage={getCarouselButtonActionError(button)}
936
+ errorMessage={getCarouselButtonActionError(
937
+ button,
938
+ showCarouselValidationErrors
939
+ || Boolean(button?.hasAttemptedSave)
940
+ || Boolean(button?.hasTouchedAction),
941
+ )}
907
942
  />
908
943
  </CapColumn>
909
944
  </CapRow>
@@ -1077,11 +1112,11 @@ export const Viber = (props) => {
1077
1112
  ...(isMediaTypeCarousel && {
1078
1113
  type: VIBER_MEDIA_TYPES.CAROUSEL,
1079
1114
  cards: carouselCards.map((card) => ({
1080
- text: card.text,
1081
- mediaUrl: card.mediaUrl,
1082
- buttons: (card.buttons || []).map((button) => ({
1083
- title: button.title,
1084
- action: button.action,
1115
+ text: card?.text ?? '',
1116
+ mediaUrl: card?.mediaUrl ?? '',
1117
+ buttons: (card?.buttons ?? []).map((button) => ({
1118
+ title: button?.title ?? '',
1119
+ action: button?.action ?? '',
1085
1120
  })),
1086
1121
  })),
1087
1122
  }),
@@ -1290,11 +1325,11 @@ export const Viber = (props) => {
1290
1325
  if (isMediaTypeCarousel) {
1291
1326
  messageData.type = VIBER_MEDIA_TYPES.CAROUSEL;
1292
1327
  messageData.cards = carouselCards.map((card) => ({
1293
- text: card.text,
1294
- mediaUrl: card.mediaUrl,
1295
- buttons: (card.buttons || []).map((button) => ({
1296
- title: button.title,
1297
- action: button.action,
1328
+ text: card?.text ?? '',
1329
+ mediaUrl: card?.mediaUrl ?? '',
1330
+ buttons: (card?.buttons ?? []).map((button) => ({
1331
+ title: button?.title ?? '',
1332
+ action: button?.action ?? '',
1298
1333
  })),
1299
1334
  }));
1300
1335
  }
@@ -1397,6 +1432,20 @@ export const Viber = (props) => {
1397
1432
  });
1398
1433
  };
1399
1434
 
1435
+ const hasCarouselValidationError = isCarouselCardCountInvalid || hasInvalidCarouselCard || hasInvalidCarouselButton;
1436
+ const getDoneHandler = () => {
1437
+ const doneCallback = onDoneCallback();
1438
+ return () => {
1439
+ if (isMediaTypeCarousel) {
1440
+ setShowCarouselValidationErrors(true);
1441
+ if (hasCarouselValidationError) {
1442
+ return;
1443
+ }
1444
+ }
1445
+ doneCallback();
1446
+ };
1447
+ };
1448
+
1400
1449
  const isDisableDone = () => {
1401
1450
  // textbox area should not empty and should have max 1000 charactor
1402
1451
  if (messageContent?.trim() === '' || errorMessageTextarea) {
@@ -1476,7 +1525,7 @@ export const Viber = (props) => {
1476
1525
  </CapRow>
1477
1526
  <ViberFooter>
1478
1527
  <CapButton
1479
- onClick={onDoneCallback()}
1528
+ onClick={getDoneHandler()}
1480
1529
  disabled={isDisableDone()}
1481
1530
  className="create-msg viber-create-msg"
1482
1531
  >
@@ -220,7 +220,7 @@
220
220
  }
221
221
 
222
222
  .viber-carousel-url-type-dropdown.ant-select-dropdown {
223
- min-width: 12rem !important;
223
+ min-width: 12rem;
224
224
  }
225
225
 
226
226
  .viber-carousel-url-type-dropdown .ant-select-dropdown-menu-item {
@@ -383,4 +383,84 @@ describe('Test Viber container', () => {
383
383
  const doneBtn = screen.getByRole('button', { name: /done/i });
384
384
  expect(doneBtn).toBeEnabled();
385
385
  });
386
+
387
+ it('does not show empty URL error on focus or while typing in carousel button action', async () => {
388
+ renderComponent({
389
+ actions: mockActions,
390
+ globalActions: mockGlobalActions,
391
+ templateData: { mode: 'create' },
392
+ viber: {
393
+ uploadedAssetData: {},
394
+ createTemplateInProgress: false,
395
+ },
396
+ location: {
397
+ pathname: '/sms/edit',
398
+ query: { type: false, module: 'default' },
399
+ search: '',
400
+ },
401
+ isFullMode: true,
402
+ handleClose: jest.fn(),
403
+ });
404
+
405
+ fireEvent.click(screen.getByRole('radio', { name: /carousel/i }));
406
+ const actionInput = await screen.findByPlaceholderText('https://example.com/action');
407
+
408
+ fireEvent.focus(actionInput);
409
+ expect(screen.queryByText("URL can't be empty")).not.toBeInTheDocument();
410
+
411
+ fireEvent.change(actionInput, { target: { value: 'https://' } });
412
+ expect(screen.queryByText("URL can't be empty")).not.toBeInTheDocument();
413
+ });
414
+
415
+ it('shows empty URL error on blur when carousel button action is left empty', async () => {
416
+ renderComponent({
417
+ actions: mockActions,
418
+ globalActions: mockGlobalActions,
419
+ templateData: { mode: 'create' },
420
+ viber: {
421
+ uploadedAssetData: {},
422
+ createTemplateInProgress: false,
423
+ },
424
+ location: {
425
+ pathname: '/sms/edit',
426
+ query: { type: false, module: 'default' },
427
+ search: '',
428
+ },
429
+ isFullMode: true,
430
+ handleClose: jest.fn(),
431
+ });
432
+
433
+ fireEvent.click(screen.getByRole('radio', { name: /carousel/i }));
434
+ const actionInput = await screen.findByPlaceholderText('https://example.com/action');
435
+
436
+ fireEvent.focus(actionInput);
437
+ fireEvent.blur(actionInput);
438
+
439
+ expect(await screen.findByText("URL can't be empty")).toBeInTheDocument();
440
+ });
441
+
442
+ it('shows empty URL error on save when carousel button action is empty', async () => {
443
+ renderComponent({
444
+ actions: mockActions,
445
+ globalActions: mockGlobalActions,
446
+ templateData: { mode: 'create' },
447
+ viber: {
448
+ uploadedAssetData: {},
449
+ createTemplateInProgress: false,
450
+ },
451
+ location: {
452
+ pathname: '/sms/edit',
453
+ query: { type: false, module: 'default' },
454
+ search: '',
455
+ },
456
+ isFullMode: true,
457
+ handleClose: jest.fn(),
458
+ });
459
+
460
+ fireEvent.click(screen.getByRole('radio', { name: /carousel/i }));
461
+ await screen.findByPlaceholderText('https://example.com/action');
462
+ fireEvent.click(screen.getByRole('button', { name: /save/i }));
463
+
464
+ expect(await screen.findByText("URL can't be empty")).toBeInTheDocument();
465
+ });
386
466
  });