@capillarytech/creatives-library 9.0.56-alpha.7 → 9.0.56-alpha.8

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 (44) hide show
  1. package/package.json +1 -1
  2. package/v2Components/CapActionButton/index.js +73 -60
  3. package/v2Components/CapActionButton/index.scss +44 -28
  4. package/v2Components/CapActionButton/messages.js +7 -3
  5. package/v2Components/CapActionButton/tests/index.test.js +17 -1
  6. package/v2Components/CapWhatsappCTA/messages.js +4 -0
  7. package/v2Components/CapWhatsappCarouselButton/index.js +42 -33
  8. package/v2Components/CapWhatsappCarouselButton/index.scss +44 -2
  9. package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +9 -1
  10. package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +58 -0
  11. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +3 -0
  12. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +19 -4
  13. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +8 -4
  14. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +11 -0
  15. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +26 -0
  16. package/v2Containers/CreativesContainer/SlideBoxContent.js +15 -0
  17. package/v2Containers/CreativesContainer/SlideBoxFooter.js +6 -2
  18. package/v2Containers/CreativesContainer/index.js +27 -0
  19. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +29 -0
  20. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +13 -0
  21. package/v2Containers/MobilePush/Create/index.js +18 -1
  22. package/v2Containers/MobilePush/Create/test/contentValidity.test.js +96 -0
  23. package/v2Containers/MobilePush/Edit/index.js +18 -1
  24. package/v2Containers/MobilePush/Edit/test/contentValidity.test.js +116 -0
  25. package/v2Containers/MobilePush/commonMethods.js +49 -1
  26. package/v2Containers/MobilePushNew/index.js +29 -4
  27. package/v2Containers/MobilePushNew/tests/index.test.js +119 -0
  28. package/v2Containers/MobilePushNew/tests/utils.test.js +82 -0
  29. package/v2Containers/MobilePushNew/utils.js +34 -1
  30. package/v2Containers/MobilepushWrapper/index.js +3 -1
  31. package/v2Containers/Rcs/index.js +34 -0
  32. package/v2Containers/Rcs/index.scss +15 -0
  33. package/v2Containers/Rcs/tests/index.test.js +67 -0
  34. package/v2Containers/Sms/Create/index.js +3 -1
  35. package/v2Containers/Sms/Edit/index.js +25 -0
  36. package/v2Containers/Sms/Edit/tests/index.test.js +85 -0
  37. package/v2Containers/Viber/index.js +24 -1
  38. package/v2Containers/Viber/tests/index.test.js +103 -0
  39. package/v2Containers/WebPush/Create/index.js +24 -0
  40. package/v2Containers/WebPush/Create/tests/contentValidity.test.js +294 -0
  41. package/v2Containers/Whatsapp/index.js +19 -1
  42. package/v2Containers/Whatsapp/tests/index.test.js +115 -0
  43. package/v2Containers/Zalo/index.js +28 -1
  44. package/v2Containers/Zalo/tests/index.test.js +99 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "9.0.56-alpha.7",
4
+ "version": "9.0.56-alpha.8",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -101,10 +101,12 @@ export const CapActionButton = (props) => {
101
101
  const onButtonTextChange = ({target}) => {
102
102
  const { value, id } = target;
103
103
  let errorMessage = '';
104
- if (value.length > BTN_MAX_LENGTH) {
104
+ if (!value.trim()) {
105
+ errorMessage = formatMessage(messages.ctaButtonTextEmptyMessage);
106
+ } else if (!isValidText(value)) {
107
+ errorMessage = formatMessage(messages.ctaButtonTextErrorMessage);
108
+ } else if (value.length > BTN_MAX_LENGTH) {
105
109
  errorMessage = formatMessage(messages.ctaButtonTextLengthError, { maxLength: BTN_MAX_LENGTH });
106
- } if (!isValidText(value)) {
107
- errorMessage = formatMessage(messages.ctaButtonErrorMessage);
108
110
  }
109
111
  setButtonError(errorMessage);
110
112
  updateDisplayAndPostback(value, id);
@@ -446,12 +448,11 @@ export const CapActionButton = (props) => {
446
448
  gutter={9}
447
449
  >
448
450
  <CapColumn
449
- span={1}
450
- className={`${ctaIsPhone ? 'whatsapp-saved-cta-phone-icon' : ''}`}
451
+ className={`rcs-saved-cta-cell ${ctaIsPhone ? 'whatsapp-saved-cta-phone-icon' : ''}`}
451
452
  >
452
453
  <CapIcon size="s" type={ctaIsPhone ? 'call' : (ctaIsReply ? 'small-link' : 'open-in-new')} />
453
454
  </CapColumn>
454
- <CapColumn span={6}>
455
+ <CapColumn className="rcs-saved-cta-cell rcs-saved-cta-text-cell">
455
456
  <CapLabel
456
457
  type="label2"
457
458
  className="rcs-saved-cta-button-text"
@@ -459,64 +460,76 @@ export const CapActionButton = (props) => {
459
460
  {text}
460
461
  </CapLabel>
461
462
  </CapColumn>
462
- <CapColumn flex={1} className="rcs-saved-cta-value-column">
463
- <div className="rcs-saved-cta-value-actions">
464
- {ctaIsPhone && (
465
- <CapLabel className="phone">+{phoneNumber}</CapLabel>
466
- )}
467
- {!ctaIsPhone && !ctaIsReply && (
468
- <>
469
- <CapLabel className="url-type" type="label2">
470
- {(cta.urlType || RCS_CTA_URL_TYPE.STATIC) === RCS_CTA_URL_TYPE.DYNAMIC
471
- ? formatMessage(messages.ctaWebsiteTypeDynamic)
472
- : formatMessage(messages.ctaWebsiteTypeStatic)}
473
- </CapLabel>
474
- <CapTooltip title={url} placement="top">
475
- <CapLabel className="url">{url}</CapLabel>
476
- </CapTooltip>
477
- </>
478
- )}
479
- {isDynamicUrlSubtype && (url || '').includes(DYNAMIC_URL_SUFFIX) && (
480
- <TagList
481
- className="rcs-cta-taglist"
482
- label={formatMessage(messages.ctaTagListLabel)}
483
- onTagSelect={(data) => onTagSelect(data, index, url)}
484
- location={location}
485
- tags={tags}
486
- injectedTags={injectedTags}
487
- selectedOfferDetails={selectedOfferDetails}
488
- onContextChange={onContextChange}
463
+ <CapColumn className="rcs-saved-cta-cell rcs-saved-cta-type-cell">
464
+ {!ctaIsPhone && !ctaIsReply && (
465
+ <CapLabel className="url-type" type="label2">
466
+ {(cta.urlType || RCS_CTA_URL_TYPE.STATIC) === RCS_CTA_URL_TYPE.DYNAMIC
467
+ ? formatMessage(messages.ctaWebsiteTypeDynamic)
468
+ : formatMessage(messages.ctaWebsiteTypeStatic)}
469
+ </CapLabel>
470
+ )}
471
+ </CapColumn>
472
+ <CapColumn className="rcs-saved-cta-cell rcs-saved-cta-url-cell">
473
+ {ctaIsPhone && (
474
+ <CapLabel className="phone">+{phoneNumber}</CapLabel>
475
+ )}
476
+ {!ctaIsPhone && !ctaIsReply && (
477
+ <CapTooltip
478
+ title={url}
479
+ placement="top"
480
+ wrapperClassName="rcs-cta-url-tooltip-wrapper"
481
+ triggerClassName="rcs-cta-url-tooltip-trigger"
482
+ >
483
+ <CapLabel className="url">{url}</CapLabel>
484
+ </CapTooltip>
485
+ )}
486
+ </CapColumn>
487
+ <CapColumn className="rcs-saved-cta-cell rcs-saved-cta-actions-cell">
488
+ {isDynamicUrlSubtype && (url || '').includes(DYNAMIC_URL_SUFFIX) && (
489
+ <TagList
490
+ className="rcs-cta-taglist"
491
+ label={formatMessage(messages.ctaTagListLabel)}
492
+ onTagSelect={(data) => onTagSelect(data, index, url)}
493
+ location={location}
494
+ tags={tags}
495
+ injectedTags={injectedTags}
496
+ selectedOfferDetails={selectedOfferDetails}
497
+ onContextChange={onContextChange}
498
+ />
499
+ )}
500
+ {isDynamicUrlSubtype && !(url || '').includes(DYNAMIC_URL_SUFFIX) && (
501
+ <CapTooltip title={formatMessage(messages.ctaTagListRevert)} placement="top">
502
+ <CapIcon
503
+ size="s"
504
+ type="return"
505
+ className="rcs-cta-tag-revert"
506
+ onClick={() => revertTagSelect(index, url)}
489
507
  />
490
- )}
491
- {isDynamicUrlSubtype && !(url || '').includes(DYNAMIC_URL_SUFFIX) && (
492
- <CapTooltip title={formatMessage(messages.ctaTagListRevert)} placement="top">
493
- <CapIcon
494
- size="s"
495
- type="return"
496
- className="rcs-cta-tag-revert"
497
- onClick={() => revertTagSelect(index, url)}
498
- />
499
- </CapTooltip>
500
- )}
501
- {(isFullMode && !isEditFlow) && (
502
- <div className="rcs-saved-cta-action-icons">
508
+ </CapTooltip>
509
+ )}
510
+ {(isFullMode && !isEditFlow) && (
511
+ <CapRow
512
+ className="rcs-saved-cta-action-icons"
513
+ type="flex"
514
+ align="middle"
515
+ noWrap
516
+ >
517
+ <CapIcon
518
+ size="s"
519
+ type="edit"
520
+ className="rcs-saved-cta-edit-icon"
521
+ onClick={() => editCta(index)}
522
+ />
523
+ {!(isHostIcs && index === 0) && !hideDeleteForSuggestionIndex(index) && !cannotDeleteSavedMandatory(index) && (
503
524
  <CapIcon
504
525
  size="s"
505
- type="edit"
506
- className="rcs-saved-cta-edit-icon"
507
- onClick={() => editCta(index)}
526
+ type="delete"
527
+ className="rcs-saved-cta-delete-icon"
528
+ onClick={() => deleteButtonHandler(index)}
508
529
  />
509
- {!(isHostIcs && index === 0) && !hideDeleteForSuggestionIndex(index) && !cannotDeleteSavedMandatory(index) && (
510
- <CapIcon
511
- size="s"
512
- type="delete"
513
- className="rcs-saved-cta-delete-icon"
514
- onClick={() => deleteButtonHandler(index)}
515
- />
516
- )}
517
- </div>
518
- )}
519
- </div>
530
+ )}
531
+ </CapRow>
532
+ )}
520
533
  </CapColumn>
521
534
  </CapRow>
522
535
  );
@@ -25,37 +25,51 @@
25
25
  margin-bottom: $CAP_SPACE_12;
26
26
  flex-wrap: nowrap;
27
27
 
28
- // Icon/text columns keep a fixed span; this column takes whatever space
29
- // is left (flex: 1, set via CapColumn's `flex` prop) instead of a fixed
30
- // span that has to add up to exactly 24 alongside its siblings — with a
31
- // fixed span, the gutter/margin between columns had nowhere to come from
32
- // and pushed this column's content past the card's right border.
33
- .rcs-saved-cta-value-column {
28
+ .rcs-saved-cta-cell {
29
+ display: flex;
30
+ align-items: center;
34
31
  min-width: 0;
32
+
33
+ // Cell children are flex items too — without this their own min-content
34
+ // (the full text/URL) makes the cell unshrinkable and pushes the row.
35
+ > * {
36
+ min-width: 0;
37
+ }
35
38
  }
36
39
 
37
- // Value text and edit/delete icons share one flex row, right-aligned as a
38
- // unit — keeps the value flush against the icons regardless of its length,
39
- // instead of the old fixed-span column + absolutely-positioned icon combo
40
- // (which drifted out of alignment with variable-length URLs/numbers).
41
- .rcs-saved-cta-value-actions {
42
- display: flex;
43
- align-items: center;
40
+ // Fixed basis for the 20-char button text cap.
41
+ .rcs-saved-cta-text-cell {
42
+ flex: 0 1 10rem;
43
+ }
44
+
45
+ // Fixed, not auto: "Static" is narrower than "Dynamic", which left the URL
46
+ // starting at a different x per row.
47
+ .rcs-saved-cta-type-cell {
48
+ flex: 0 1 4rem;
49
+ }
50
+
51
+ .rcs-saved-cta-url-cell {
52
+ flex: 1 1 0;
53
+ }
54
+
55
+ .rcs-saved-cta-actions-cell {
56
+ flex: 0 0 auto;
44
57
  justify-content: flex-end;
45
58
  gap: $CAP_SPACE_08;
46
- width: 100%;
47
- min-width: 0;
59
+ }
48
60
 
49
- .url-type {
50
- flex-shrink: 0;
51
- }
61
+ // CapTooltip nests `.url` two levels down, so these — not `.url` — are the
62
+ // boxes that must be allowed to shrink for the ellipsis to apply.
63
+ .rcs-cta-url-tooltip-wrapper {
64
+ flex: 1 1 0;
65
+ min-width: 0;
66
+ }
52
67
 
53
- .url, .phone {
54
- min-width: 0;
55
- }
68
+ .rcs-cta-url-tooltip-trigger {
69
+ min-width: 0;
56
70
  }
57
71
 
58
- .url {
72
+ .url, .rcs-saved-cta-button-text, .url-type {
59
73
  white-space: nowrap;
60
74
  overflow: hidden;
61
75
  text-overflow: ellipsis;
@@ -69,10 +83,6 @@
69
83
  word-break: break-word;
70
84
  }
71
85
 
72
- .saved-button-text {
73
- font-weight: 500;
74
- }
75
-
76
86
  .rcs-saved-cta-action-icons {
77
87
  display: flex;
78
88
  align-items: center;
@@ -85,8 +95,6 @@
85
95
  cursor: pointer;
86
96
  }
87
97
 
88
- // Dynamic-URL tag controls: keep them from stretching the flex row's
89
- // height/width — same treatment as the edit/delete icons above.
90
98
  .rcs-cta-tag-revert {
91
99
  display: flex;
92
100
  align-items: center;
@@ -95,7 +103,15 @@
95
103
  }
96
104
 
97
105
  .rcs-cta-taglist {
106
+ display: flex;
107
+ align-items: center;
98
108
  flex-shrink: 0;
109
+
110
+ // CapTagList floats this trigger right, taking it out of flow, which
111
+ // let it paint over the edit icon instead of occupying real width.
112
+ .tooltip-add-label-container {
113
+ float: none;
114
+ }
99
115
  }
100
116
  }
101
117
 
@@ -83,9 +83,13 @@ export default defineMessages({
83
83
  id: `${prefix}.comingSoonTooltip`,
84
84
  defaultMessage: 'Right now only one Call to action button is enabled, in future you would be able to add upto 3 buttons.',
85
85
  },
86
- ctaButtonErrorMessage: {
87
- id: `${prefix}.ctaButtonErrorMessage`,
88
- defaultMessage: 'Button text cannot be empty',
86
+ ctaButtonTextEmptyMessage: {
87
+ id: `${prefix}.ctaButtonTextEmptyMessage`,
88
+ defaultMessage: 'Button text cannot be empty or contain only whitespace',
89
+ },
90
+ ctaButtonTextErrorMessage: {
91
+ id: `${prefix}.ctaButtonTextErrorMessage`,
92
+ defaultMessage: 'Newlines, Emojis, or Special characters are not allowed',
89
93
  },
90
94
  ctaButtonTextLengthError: {
91
95
  id: `${prefix}.ctaButtonTextLengthError`,
@@ -521,7 +521,23 @@ describe('CapActionButton', () => {
521
521
  initializeComponent([initial]);
522
522
  const buttontextInput = screen.getByPlaceholderText('Enter button text');
523
523
  fireEvent.change(buttontextInput, { target: { value: '&*^', id: 0 } });
524
- expect(screen.getByText(/button text cannot be empty/i)).toBeInTheDocument();
524
+ expect(screen.getByText(/special characters are not allowed/i)).toBeInTheDocument();
525
+ });
526
+
527
+ it('should show empty error for whitespace-only button text (onButtonTextChange)', () => {
528
+ const initial = {
529
+ index: 0,
530
+ type: RCS_BUTTON_TYPES.QUICK_REPLY,
531
+ text: 'Shop',
532
+ phoneNumber: '',
533
+ url: '',
534
+ postback: 'Shop',
535
+ isSaved: false,
536
+ };
537
+ initializeComponent([initial]);
538
+ const buttontextInput = screen.getByPlaceholderText('Enter button text');
539
+ fireEvent.change(buttontextInput, { target: { value: ' ', id: 0 } });
540
+ expect(screen.getByText(/cannot be empty or contain only whitespace/i)).toBeInTheDocument();
525
541
  });
526
542
 
527
543
  it('should show error for invalid url (onUrlChange)', () => {
@@ -57,6 +57,10 @@ export default defineMessages({
57
57
  id: `${prefix}.ctaButtonErrorMessage`,
58
58
  defaultMessage: "Newlines, Emojis, or Special characters are not allowed",
59
59
  },
60
+ ctaButtonTextEmptyMessage: {
61
+ id: `${prefix}.ctaButtonTextEmptyMessage`,
62
+ defaultMessage: 'Button text cannot be empty or contain only whitespace',
63
+ },
60
64
  addButton: {
61
65
  id: `${prefix}.addButton`,
62
66
  defaultMessage: 'Add button',
@@ -126,7 +126,9 @@ export const CapWhatsappCarouselButton = (props) => {
126
126
 
127
127
  const onButtonTextChange = (value, buttonIndex) => {
128
128
  let errorMessage = '';
129
- if (!isValidWhatsappCtaText(value)) {
129
+ if (!value.trim()) {
130
+ errorMessage = formatMessage(capWhatsappCTAMsg.ctaButtonTextEmptyMessage);
131
+ } else if (!isValidWhatsappCtaText(value)) {
130
132
  errorMessage = formatMessage(capWhatsappCTAMsg.ctaButtonErrorMessage);
131
133
  }
132
134
  onValueChange(buttonIndex, [{fieldName: 'text', value}, {fieldName: 'textError', value: errorMessage}]);
@@ -333,12 +335,11 @@ export const CapWhatsappCarouselButton = (props) => {
333
335
  noWrap
334
336
  >
335
337
  <CapColumn
336
- span={1}
337
- className={`${isPhoneNumberType ? 'whatsapp-saved-cta-phone-icon' : ''}`}
338
+ className={`whatsapp-saved-cta-cell ${isPhoneNumberType ? 'whatsapp-saved-cta-phone-icon' : ''}`}
338
339
  >
339
340
  <CapIcon size="s" type={isPhoneNumberType ? 'call' : isURLtype ? 'launch' : 'six-dots'} />
340
341
  </CapColumn>
341
- <CapColumn span={6}>
342
+ <CapColumn className="whatsapp-saved-cta-cell whatsapp-saved-cta-text-cell">
342
343
  <CapLabel
343
344
  type="label2"
344
345
  className="whatsapp-saved-cta-button-text"
@@ -346,39 +347,47 @@ export const CapWhatsappCarouselButton = (props) => {
346
347
  {button?.text}
347
348
  </CapLabel>
348
349
  </CapColumn>
349
- {isPhoneNumberType && (
350
- <CapColumn span={10} align="left">
350
+ <CapColumn className="whatsapp-saved-cta-cell whatsapp-saved-cta-type-cell">
351
+ {isURLtype && (
352
+ <CapLabel className="url-type" type="label2">
353
+ {button?.urlType === STATIC_URL
354
+ ? formatMessage(capWhatsappCTAMsg.ctaWebsiteTypeStatic)
355
+ : formatMessage(capWhatsappCTAMsg.ctaWebsiteTypeDynamic)}
356
+ </CapLabel>
357
+ )}
358
+ </CapColumn>
359
+ <CapColumn className="whatsapp-saved-cta-cell whatsapp-saved-cta-url-cell whatsapp-url-preview-wrapper">
360
+ {isPhoneNumberType && (
351
361
  <CapLabel className="phone">
352
362
  {button?.phone_number}
353
363
  </CapLabel>
354
- </CapColumn>
355
- )}
356
- {isURLtype && (
357
- <CapRow type="flex" align="middle">
358
- <CapRow type="flex" align="middle" justify="start">
359
- <CapLabel className="url-type" type="label2">
360
- {button?.urlType === STATIC_URL
361
- ? formatMessage(capWhatsappCTAMsg.ctaWebsiteTypeStatic)
362
- : formatMessage(capWhatsappCTAMsg.ctaWebsiteTypeDynamic)}
363
- </CapLabel>
364
- </CapRow>
365
- <CapRow type='flex' className="whatsapp-url-preview-wrapper">
366
- <CapTooltip title={button?.url} placement="top">
367
- <CapColumn span={16} className="margin-r-0">
368
- <CapLabel className="url">{button?.url}</CapLabel>
369
- </CapColumn>
370
- </CapTooltip>
364
+ )}
365
+ {isURLtype && (
366
+ <CapTooltip
367
+ title={button?.url}
368
+ placement="top"
369
+ wrapperClassName="whatsapp-cta-url-tooltip-wrapper"
370
+ triggerClassName="whatsapp-cta-url-tooltip-trigger"
371
+ >
372
+ <CapLabel className="url">{button?.url}</CapLabel>
373
+ </CapTooltip>
374
+ )}
375
+ </CapColumn>
376
+ <CapColumn className="whatsapp-saved-cta-cell whatsapp-saved-cta-actions-cell">
377
+ {!isEditFlow && (
378
+ <CapRow
379
+ className="whatsapp-saved-cta-action-icons"
380
+ type="flex"
381
+ align="middle"
382
+ noWrap
383
+ >
384
+ <CapIcon size="s" type="edit" onClick={() => handleEditButton(index)}/>
385
+ {parseInt(carouselIndex, 10) === 0 && (
386
+ <CapIcon size="s" type="delete" onClick={() => handleDeleteButton(index)} disabled={buttonData?.length === 1}/>
387
+ )}
371
388
  </CapRow>
372
- </CapRow>
373
- )}
374
- {!isEditFlow && (
375
- <CapRow type='flex' align='middle' justify="end" gap={12} fillSpace>
376
- <CapIcon size="s" type="edit" onClick={() => handleEditButton(index)}/>
377
- {parseInt(carouselIndex, 10) === 0 && (
378
- <CapIcon size="s" type="delete" onClick={() => handleDeleteButton(index)} disabled={buttonData?.length === 1}/>
379
- )}
380
- </CapRow>
381
- )}
389
+ )}
390
+ </CapColumn>
382
391
  {isEditFlow && button?.urlType === DYNAMIC_URL && button?.url.includes('{{1}}') && (
383
392
  <CapColumn>
384
393
  <TagList
@@ -37,7 +37,49 @@
37
37
  height: auto;
38
38
  }
39
39
 
40
- .whatsapp-url-preview-wrapper {
41
- max-width: 5.714rem;
40
+ .whatsapp-saved-cta-cell {
41
+ display: flex;
42
+ align-items: center;
43
+ min-width: 0;
44
+
45
+ > * {
46
+ min-width: 0;
47
+ }
48
+ }
49
+
50
+ .whatsapp-saved-cta-text-cell {
51
+ flex: 0 1 10rem;
52
+ }
53
+
54
+ .whatsapp-saved-cta-type-cell {
55
+ flex: 0 1 5.5rem;
56
+ }
57
+
58
+ .whatsapp-saved-cta-url-cell {
59
+ flex: 1 1 0;
60
+ }
61
+
62
+ .whatsapp-saved-cta-actions-cell {
63
+ flex: 0 0 4rem;
64
+ justify-content: flex-end;
65
+ }
66
+
67
+ .whatsapp-saved-cta-action-icons {
68
+ display: flex;
69
+ align-items: center;
70
+ flex-shrink: 0;
71
+ gap: $CAP_SPACE_12;
72
+ }
73
+
74
+ // CapTooltip nests `.url` two levels down, so these — not `.url` — are the
75
+ // boxes that must be allowed to shrink for the ellipsis to apply.
76
+ .whatsapp-cta-url-tooltip-wrapper {
77
+ flex: 1 1 0;
78
+ min-width: 0;
79
+ }
80
+
81
+ .whatsapp-cta-url-tooltip-trigger {
82
+ width: 100%;
83
+ min-width: 0;
42
84
  }
43
85
  }
@@ -6,7 +6,7 @@
6
6
  * - Configured state: shows a two-column CapCard with pen icon, channel preview (left), dynamic controls (right)
7
7
  */
8
8
 
9
- import React, { useState, useCallback } from 'react';
9
+ import React, { useState, useCallback, useEffect } from 'react';
10
10
  import PropTypes from 'prop-types';
11
11
  import { injectIntl } from 'react-intl';
12
12
  import { CAP_G09 } from '@capillarytech/cap-ui-library/styled/variables';
@@ -82,6 +82,14 @@ const CommunicationFlowCard = ({
82
82
  const [showSlideBox, setShowSlideBox] = useState(false);
83
83
  const [savedData, setSavedData] = useState(initialData || null);
84
84
 
85
+ // On a hard reload of an edit page, initialData is available but savedData is not yet set.
86
+ // This effect ensures that savedData is initialized from initialData in that case.
87
+ useEffect(() => {
88
+ if (initialData && !savedData) {
89
+ setSavedData(initialData);
90
+ }
91
+ }, [initialData]);
92
+
85
93
  const handleSave = useCallback((data) => {
86
94
  setSavedData(data);
87
95
  setShowSlideBox(false);
@@ -652,4 +652,62 @@ describe('CommunicationFlowCard', () => {
652
652
  expect(screen.getByText('Message:')).toBeInTheDocument();
653
653
  });
654
654
  });
655
+
656
+ describe('async initialData (edit page hard reload)', () => {
657
+ // On a hard reload of an edit page, the consumer's own by-id fetch hasn't
658
+ // resolved at first mount, so initialData starts as null and arrives a
659
+ // render or two later. savedData must pick it up then, not just at mount.
660
+ it('shows the summary card once initialData arrives after mount, instead of staying stuck on the empty "Add creatives" state', () => {
661
+ const { rerender } = render(
662
+ <IntlProvider locale="en" messages={{}} defaultLocale="en">
663
+ <CommunicationFlowCard
664
+ config={baseConfig}
665
+ initialData={null}
666
+ onSave={jest.fn()}
667
+ onCancel={jest.fn()}
668
+ onChange={jest.fn()}
669
+ cap={{}}
670
+ />
671
+ </IntlProvider>,
672
+ );
673
+ expect(screen.getByText('Add creatives')).toBeInTheDocument();
674
+
675
+ rerender(
676
+ <IntlProvider locale="en" messages={{}} defaultLocale="en">
677
+ <CommunicationFlowCard
678
+ config={baseConfig}
679
+ initialData={makeSavedData()}
680
+ onSave={jest.fn()}
681
+ onCancel={jest.fn()}
682
+ onChange={jest.fn()}
683
+ cap={{}}
684
+ />
685
+ </IntlProvider>,
686
+ );
687
+ expect(screen.getByText('Message:')).toBeInTheDocument();
688
+ expect(screen.queryByText('Add creatives')).not.toBeInTheDocument();
689
+ });
690
+
691
+ it('does not clobber content the user already saved locally when a later render passes a different initialData', () => {
692
+ const { rerender } = renderCard({ initialData: null });
693
+ fireEvent.click(screen.getByText('Add creatives'));
694
+ fireEvent.click(screen.getByTestId('flow-save'));
695
+ expect(screen.getByText('Message:')).toBeInTheDocument();
696
+
697
+ rerender(
698
+ <IntlProvider locale="en" messages={{}} defaultLocale="en">
699
+ <CommunicationFlowCard
700
+ config={baseConfig}
701
+ initialData={makeSavedData({ contentItems: [{ channel: 'EMAIL', templateData: {} }] })}
702
+ onSave={jest.fn()}
703
+ onCancel={jest.fn()}
704
+ onChange={jest.fn()}
705
+ cap={{}}
706
+ />
707
+ </IntlProvider>,
708
+ );
709
+ // Locally-saved SMS content must survive; the later initialData prop must not overwrite it.
710
+ expect(screen.getByText('SMS')).toBeInTheDocument();
711
+ });
712
+ });
655
713
  });
@@ -251,6 +251,7 @@ const ChannelSelectionStep = ({
251
251
  };
252
252
 
253
253
  const handleChannelsVisibleChange = (visible) => {
254
+ if (visible && renderChannelDropdownOverlay() === null) return;
254
255
  setShowChannelsMenu(visible);
255
256
  };
256
257
 
@@ -438,6 +439,7 @@ const ChannelSelectionStep = ({
438
439
  overlay={renderChannelDropdownOverlay() || <CapMenu />}
439
440
  onVisibleChange={handleChannelsVisibleChange}
440
441
  visible={showChannelsMenu}
442
+ trigger={['click']}
441
443
  >
442
444
  <CapButton
443
445
  isAddBtn
@@ -482,6 +484,7 @@ const ChannelSelectionStep = ({
482
484
  deliverySetting={value?.deliverySetting}
483
485
  onDeliverySettingChange={(deliverySetting) => onChange({ deliverySetting })}
484
486
  onDomainPropertiesLoaded={setDomainPropertiesData}
487
+ orgUnitId={config?.context?.ouId}
485
488
  intl={intl}
486
489
  />
487
490
  )}