@capillarytech/creatives-library 9.0.28 → 9.0.29-alpha.1

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 (149) hide show
  1. package/constants/unified.js +29 -0
  2. package/global-styles.js +1 -1
  3. package/package.json +1 -1
  4. package/services/tests/api.test.js +35 -20
  5. package/utils/commonUtils.js +22 -1
  6. package/utils/rcsPayloadUtils.js +102 -0
  7. package/utils/templateVarUtils.js +198 -0
  8. package/utils/tests/rcsPayloadUtils.test.js +295 -0
  9. package/utils/tests/templateVarUtils.test.js +204 -0
  10. package/v2Components/CapActionButton/constants.js +21 -1
  11. package/v2Components/CapActionButton/index.js +260 -144
  12. package/v2Components/CapActionButton/index.scss +245 -14
  13. package/v2Components/CapActionButton/messages.js +32 -3
  14. package/v2Components/CapActionButton/tests/index.test.js +74 -19
  15. package/v2Components/CapImageUpload/index.js +2 -2
  16. package/v2Components/CapTagList/index.js +9 -0
  17. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -48
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
  22. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +58 -11
  24. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  25. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +186 -22
  26. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +381 -80
  27. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +155 -8
  28. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  29. package/v2Components/CommonTestAndPreview/constants.js +35 -2
  30. package/v2Components/CommonTestAndPreview/index.js +770 -231
  31. package/v2Components/CommonTestAndPreview/messages.js +45 -3
  32. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  33. package/v2Components/CommonTestAndPreview/sagas.js +25 -6
  34. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +1 -0
  35. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +412 -257
  36. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +337 -63
  37. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  38. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  39. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  40. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +108 -15
  41. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  42. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  43. package/v2Components/CommonTestAndPreview/tests/index.test.js +133 -4
  44. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  45. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
  46. package/v2Components/CommonTestAndPreview/tests/utils.test.js +151 -0
  47. package/v2Components/CommonTestAndPreview/utils.js +84 -0
  48. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
  49. package/v2Components/SmsFallback/constants.js +94 -0
  50. package/v2Components/SmsFallback/index.js +958 -0
  51. package/v2Components/SmsFallback/index.scss +266 -0
  52. package/v2Components/SmsFallback/messages.js +78 -0
  53. package/v2Components/SmsFallback/smsFallbackUtils.js +120 -0
  54. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  55. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  56. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  57. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +208 -0
  58. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
  59. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +471 -0
  60. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  61. package/v2Components/TemplatePreview/_templatePreview.scss +45 -25
  62. package/v2Components/TemplatePreview/constants.js +2 -0
  63. package/v2Components/TemplatePreview/index.js +147 -32
  64. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  65. package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
  66. package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
  67. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  68. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  69. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  70. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  71. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  72. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +4 -1
  73. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +4 -0
  74. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
  75. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +27 -0
  76. package/v2Containers/CreativesContainer/SlideBoxContent.js +37 -4
  77. package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
  78. package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
  79. package/v2Containers/CreativesContainer/constants.js +11 -0
  80. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +77 -0
  81. package/v2Containers/CreativesContainer/index.js +328 -106
  82. package/v2Containers/CreativesContainer/index.scss +102 -1
  83. package/v2Containers/CreativesContainer/tests/CreativesSlideBoxWrapper.test.js +58 -0
  84. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  85. package/v2Containers/CreativesContainer/tests/SlideBoxContent.smsDltPreview.test.js +73 -0
  86. package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +37 -0
  87. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +103 -34
  88. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +193 -15
  89. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +88 -0
  90. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +549 -57
  91. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  92. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  93. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  94. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  95. package/v2Containers/Email/index.js +3 -63
  96. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +4 -9
  97. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  98. package/v2Containers/Rcs/carouselUtils.js +224 -0
  99. package/v2Containers/Rcs/components/CarouselCard.js +317 -0
  100. package/v2Containers/Rcs/components/CarouselCardButtons.js +113 -0
  101. package/v2Containers/Rcs/components/CarouselCardMedia.js +136 -0
  102. package/v2Containers/Rcs/components/CarouselCharacterCount.js +31 -0
  103. package/v2Containers/Rcs/components/CarouselDimensionSelection.js +80 -0
  104. package/v2Containers/Rcs/constants.js +132 -16
  105. package/v2Containers/Rcs/index.js +1808 -949
  106. package/v2Containers/Rcs/index.scss +443 -8
  107. package/v2Containers/Rcs/messages.js +45 -22
  108. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +484 -0
  109. package/v2Containers/Rcs/tests/CarouselCard.test.js +464 -0
  110. package/v2Containers/Rcs/tests/CarouselCardButtons.test.js +211 -0
  111. package/v2Containers/Rcs/tests/CarouselCardMedia.test.js +160 -0
  112. package/v2Containers/Rcs/tests/CarouselCharacterCount.test.js +50 -0
  113. package/v2Containers/Rcs/tests/CarouselDimensionSelection.test.js +119 -0
  114. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +74176 -39409
  115. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +10 -74
  116. package/v2Containers/Rcs/tests/carouselUtils.test.js +916 -0
  117. package/v2Containers/Rcs/tests/index.test.js +219 -40
  118. package/v2Containers/Rcs/tests/mockData.js +38 -0
  119. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +873 -0
  120. package/v2Containers/Rcs/tests/utils.test.js +682 -30
  121. package/v2Containers/Rcs/utils.js +514 -12
  122. package/v2Containers/Sms/Create/index.js +115 -48
  123. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  124. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  125. package/v2Containers/SmsTrai/Create/index.js +9 -4
  126. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  127. package/v2Containers/SmsTrai/Edit/index.js +678 -169
  128. package/v2Containers/SmsTrai/Edit/index.scss +126 -0
  129. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  130. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +5615 -3014
  131. package/v2Containers/SmsWrapper/index.js +37 -8
  132. package/v2Containers/TagList/index.js +5 -0
  133. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  134. package/v2Containers/Templates/_templates.scss +196 -12
  135. package/v2Containers/Templates/actions.js +11 -0
  136. package/v2Containers/Templates/constants.js +2 -0
  137. package/v2Containers/Templates/index.js +131 -59
  138. package/v2Containers/Templates/sagas.js +57 -13
  139. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +129 -0
  140. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1060 -1015
  141. package/v2Containers/Templates/tests/sagas.test.js +199 -16
  142. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  143. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  144. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  145. package/v2Containers/TemplatesV2/index.js +88 -25
  146. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  147. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  148. package/v2Containers/Whatsapp/index.js +3 -20
  149. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
@@ -1,7 +1,20 @@
1
1
  @import '~@capillarytech/cap-ui-library/styles/_variables';
2
2
 
3
- #rcs-cta-type{
4
- width: 90%;
3
+ // Horizontal RCS button type radios (Phone number | URL | Quick reply)
4
+ .cap-rcs-cta-type-radio.cap-radio-group-v2 {
5
+ display: flex;
6
+ flex-wrap: wrap;
7
+ align-items: center;
8
+ margin-top: 0.5rem;
9
+
10
+ .ant-radio-wrapper {
11
+ margin-right: $CAP_SPACE_24;
12
+ margin-bottom: 0;
13
+
14
+ &:last-child {
15
+ margin-right: 0;
16
+ }
17
+ }
5
18
  }
6
19
 
7
20
  .cap-rcs-saved-cta {
@@ -10,13 +23,36 @@
10
23
  padding: 0.625rem;
11
24
  border-radius: $CAP_SPACE_04;
12
25
  margin-bottom: $CAP_SPACE_12;
26
+ flex-wrap: nowrap;
13
27
 
14
- div:not(:last-child) {
15
- margin-right: 0.5625rem;
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 {
34
+ min-width: 0;
16
35
  }
17
36
 
18
- .rcs-url-preview-wrapper {
19
- max-width: 5.714rem;
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;
44
+ justify-content: flex-end;
45
+ gap: $CAP_SPACE_08;
46
+ width: 100%;
47
+ min-width: 0;
48
+
49
+ .url-type {
50
+ flex-shrink: 0;
51
+ }
52
+
53
+ .url, .phone {
54
+ min-width: 0;
55
+ }
20
56
  }
21
57
 
22
58
  .url {
@@ -37,20 +73,202 @@
37
73
  font-weight: 500;
38
74
  }
39
75
 
40
- .rcs-saved-cta-edit-icon {
41
- position: absolute;
42
- right: 0.4375rem;
76
+ .rcs-saved-cta-action-icons {
77
+ display: flex;
78
+ align-items: center;
79
+ gap: $CAP_SPACE_08;
80
+ flex-shrink: 0;
43
81
  }
44
82
 
83
+ .rcs-saved-cta-edit-icon,
45
84
  .rcs-saved-cta-delete-icon {
46
- position: absolute;
47
- right: $CAP_SPACE_02;
85
+ cursor: pointer;
86
+ }
87
+
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
+ .rcs-cta-tag-revert {
91
+ display: flex;
92
+ align-items: center;
93
+ flex-shrink: 0;
94
+ cursor: pointer;
95
+ }
96
+
97
+ .rcs-cta-taglist {
98
+ flex-shrink: 0;
99
+ }
100
+ }
101
+
102
+ // Button text / URL: count via CapInput `suffix` (antd affix, right-aligned inside field).
103
+ // Phone: react-phone-input-2 — overlay count + padding on .form-control
104
+ // Selector needs `.cap-heading.h5` too — CapHeading's own `.cap-heading.h5` rule (2 classes)
105
+ // otherwise beats a single-class `.rcs-cta-inner-char-count` selector and forces dark text,
106
+ // regardless of source order (light-grey placeholder look per Figma).
107
+ .cap-heading.h5.rcs-cta-inner-char-count {
108
+ font-size: 0.75rem;
109
+ line-height: 1.25rem;
110
+ color: $FONT_COLOR_03;
111
+ white-space: nowrap;
112
+ }
113
+
114
+ // Button text: match the phone field's full-width treatment (Figma reference).
115
+ // `className` on CapInput lands on componentWithLabelHOC's OUTER wrapper div
116
+ // (confirmed via rendered DOM), not on the `.ant-input-affix-wrapper` itself —
117
+ // force the wrapper full-width directly rather than relying on descendant rules.
118
+ .rcs-cta-button-text {
119
+ width: 100%;
120
+
121
+ .ant-input-affix-wrapper {
122
+ width: 100%;
123
+ }
124
+ }
125
+
126
+ .rcs-cta-input-with-inner-count {
127
+ position: relative;
128
+ width: 100%;
129
+
130
+ &--phone .rcs-cta-inner-char-count {
131
+ position: absolute;
132
+ right: 0.6875rem;
133
+ top: 50%;
134
+ transform: translateY(-50%);
135
+ z-index: 2;
136
+ pointer-events: none;
137
+ }
138
+ }
139
+
140
+ // RCS phone field: Figma — one light border, 4px radius, ~antd Input large height, full width, count inside right
141
+ .rcs-button-cta-create-container .rcs-cta-input-with-inner-count--phone {
142
+ .react-tel-input.rcs-cta-phone-input {
143
+ width: 100%;
144
+ }
145
+
146
+ .rcs-cta-phone-input .form-control {
147
+ width: 100% !important;
148
+ max-width: none;
149
+ height: 2.5rem;
150
+ min-height: 2.5rem;
151
+ padding-top: 0;
152
+ padding-bottom: 0;
153
+ padding-left: 3rem;
154
+ padding-right: 3.5rem;
155
+ margin: 0;
156
+ font-size: 0.875rem;
157
+ line-height: 2.5rem;
158
+ color: $CAP_G01;
159
+ background: $CAP_WHITE;
160
+ border: 1px solid $CAP_G06;
161
+ border-radius: 0.25rem;
162
+ box-shadow: none;
163
+
164
+ &::placeholder {
165
+ color: $CAP_G05;
48
166
  }
167
+
168
+ &:hover {
169
+ border-color: $CAP_G11;
170
+ }
171
+
172
+ &:focus {
173
+ border-color: $CAP_G01;
174
+ box-shadow: none;
175
+ outline: none;
176
+ }
177
+ }
178
+
179
+ // The flag-dropdown sits on top of .form-control's left edge, so hovering the
180
+ // flag itself doesn't trigger `.form-control:hover` — bind the border-color
181
+ // change to the whole wrapper so the border reacts consistently everywhere.
182
+ .react-tel-input.rcs-cta-phone-input:hover .form-control {
183
+ border-color: $CAP_G11;
184
+ }
185
+
186
+ .react-tel-input.rcs-cta-phone-input:focus-within .form-control {
187
+ border-color: $CAP_G01;
188
+ }
189
+
190
+ .rcs-cta-phone-input .flag-dropdown {
191
+ // Raw px, not rem: must exactly match .form-control's 1px border width.
192
+ // At this app's 14px root font-size, 0.0625rem resolved to 0.875px — the
193
+ // sub-pixel gap left the outer border looking "cut off" right where the
194
+ // flag-dropdown's opaque background sat, most visible on hover when the
195
+ // border color changed and made the gap's anti-aliasing stand out.
196
+ top: 1px;
197
+ bottom: 1px;
198
+ left: 1px;
199
+ padding: 0;
200
+ background: $CAP_WHITE;
201
+ border: none;
202
+ border-radius: 0.1875rem 0 0 0.1875rem; // inset from outer 4px radius
203
+ }
204
+
205
+ .rcs-cta-phone-input .flag-dropdown.open {
206
+ border-radius: 0.1875rem 0 0 0;
207
+ }
208
+
209
+ .rcs-cta-phone-input .selected-flag {
210
+ width: 2.75rem;
211
+ padding: 0 0 0 0.5rem;
212
+ background: transparent;
213
+ border-radius: 0.1875rem 0 0 0.1875rem;
214
+ }
215
+
216
+ .rcs-cta-phone-input .selected-flag:hover,
217
+ .rcs-cta-phone-input .selected-flag:focus {
218
+ background: $CAP_G09;
219
+ }
220
+
221
+ .rcs-cta-phone-input .selected-flag .arrow {
222
+ left: 1.375rem;
223
+ border-top-color: $CAP_G04;
224
+ }
225
+ }
226
+
227
+ // URL CTA: URL type (narrow) + URL field (~1:3) per Figma
228
+ .rcs-cta-url-fields-row {
229
+ .rcs-cta-url-type-col .ant-select {
230
+ width: 100%;
231
+ }
49
232
  }
50
233
 
51
- .rcs-button-cta-create-container {
52
- border: solid 1px $CAP_G06;
53
- padding: 10px;
234
+ // RCS “create CTA” card: 0.5rem padding + gaps (Figma-tight; fixes excess space above first label)
235
+ .rcs-button-cta-create-container.cap-row-v2 {
236
+ display: flex;
237
+ flex-direction: column;
238
+ // Cross-axis (=width, since this is a column-flex row) defaults to `align-items: flex-start`
239
+ // via antd's `.ant-row-top`, which lets child Cols shrink to content instead of filling the
240
+ // row — same issue CapRow's own `.cap-row-vertical` guards against. Force stretch here too,
241
+ // otherwise fields with no explicit width of their own (e.g. Button text) render too narrow.
242
+ align-items: stretch;
243
+ flex-wrap: nowrap;
244
+ width: 100%;
245
+ border: solid 1px $CAP_G06;
246
+ padding: 1.25rem;
247
+ border-radius: 0.25rem; // 4px — match .cap-rcs-saved-cta
248
+
249
+ // Stack “Button type” (radios) above “Button text”
250
+ .rcs-button-cta-create.cap-row-v2 {
251
+ display: flex;
252
+ flex-direction: column;
253
+ align-items: stretch;
254
+ flex-wrap: nowrap;
255
+ margin: 0;
256
+ }
257
+
258
+ // Space after each field label (CapHeading margins are often zeroed by the library)
259
+ .cta-label {
260
+ margin-bottom: 0;
261
+ margin-top: 0.75rem;
262
+ }
263
+
264
+ .cta-label + * {
265
+ margin-top: 0.5rem;
266
+ }
267
+
268
+ // “Button type” heading has no .cta-label — keep same label→control gap as other fields
269
+ .rcs-button-cta-create > .ant-col:first-of-type .cap-rcs-cta-type-radio {
270
+ margin-top: 0.5rem;
271
+ }
54
272
  }
55
273
 
56
274
  .disabled {
@@ -65,3 +283,16 @@
65
283
  .button-disabled-tooltip-wrapper {
66
284
  display: inline-block;
67
285
  }
286
+
287
+ // Space above Save/Delete row: kept on this stable outer wrapper (not on
288
+ // .button-disabled-tooltip-wrapper) since that inner CapRow's own display
289
+ // (flex vs inline-block) flips depending on whether CapTooltip renders its
290
+ // extra trigger span (title set vs empty), which made the gap disappear
291
+ // whenever the Save button was enabled.
292
+ .rcs-cta-save-delete-btn {
293
+ margin-top: 1.25rem;
294
+
295
+ .rcs-cta-delete-btn {
296
+ margin-left: 0.75rem;
297
+ }
298
+ }
@@ -12,7 +12,7 @@ export default defineMessages({
12
12
  },
13
13
  ctaQr: {
14
14
  id: `${prefix}.ctaQr`,
15
- defaultMessage: 'Quick Reply',
15
+ defaultMessage: 'Quick reply',
16
16
  },
17
17
  ctaPhoneNo: {
18
18
  id: `${prefix}.ctaPhoneNo`,
@@ -47,9 +47,17 @@ export default defineMessages({
47
47
  id: `${prefix}.templateMessageLength`,
48
48
  defaultMessage: 'Characters count: {currentLength}/{maxLength}',
49
49
  },
50
+ ctaFieldCharCountInline: {
51
+ id: `${prefix}.ctaFieldCharCountInline`,
52
+ defaultMessage: '{current}/{max}',
53
+ },
50
54
  ctaType: {
51
55
  id: `${prefix}.ctaType`,
52
- defaultMessage: 'Type of action',
56
+ defaultMessage: 'Button type',
57
+ },
58
+ ctaUrlRadio: {
59
+ id: `${prefix}.ctaUrlRadio`,
60
+ defaultMessage: 'URL',
53
61
  },
54
62
  templateButtonTextPlaceholder: {
55
63
  id: `${prefix}.templateButtonTextPlaceholder`,
@@ -145,7 +153,15 @@ export default defineMessages({
145
153
  },
146
154
  ctaWebsiteType: {
147
155
  id: `${prefix}.ctaWebsiteType`,
148
- defaultMessage: 'URL Type',
156
+ defaultMessage: 'URL type',
157
+ },
158
+ ctaUrlField: {
159
+ id: `${prefix}.ctaUrlField`,
160
+ defaultMessage: 'URL',
161
+ },
162
+ ctaEnterUrlPlaceholder: {
163
+ id: `${prefix}.ctaEnterUrlPlaceholder`,
164
+ defaultMessage: 'Enter URL',
149
165
  },
150
166
  ctaWebsiteTypeStatic: {
151
167
  id: `${prefix}.ctaWebsiteTypeStatic`,
@@ -183,4 +199,17 @@ export default defineMessages({
183
199
  id: `${prefix}.addLabels`,
184
200
  defaultMessage: 'Add Labels',
185
201
  },
202
+ ctaDynamicUrlTooltip: {
203
+ id: `${prefix}.ctaDynamicUrlTooltip`,
204
+ defaultMessage:
205
+ 'Only one variable can be added to a URL. No need to add {one} to the end of the URL',
206
+ },
207
+ ctaTagListLabel: {
208
+ id: `${prefix}.ctaTagListLabel`,
209
+ defaultMessage: 'Add URL label',
210
+ },
211
+ ctaTagListRevert: {
212
+ id: `${prefix}.ctaTagListRevert`,
213
+ defaultMessage: 'Reset website URL label to default value',
214
+ },
186
215
  });
@@ -4,8 +4,10 @@ import '@testing-library/jest-dom';
4
4
  import { render, screen, fireEvent } from '../../../utils/test-utils';
5
5
  import { waitFor } from '@testing-library/react';
6
6
  import { CapActionButton } from '../index';
7
- import { BTN_MAX_LENGTH, PHONE_NUMBER_MAX_LENGTH, URL_MAX_LENGTH } from '../constants';
8
- import { RCS_BUTTON_TYPES } from '../../../v2Containers/Rcs/constants';
7
+ import {
8
+ BTN_MAX_LENGTH, PHONE_NUMBER_MAX_LENGTH, URL_MAX_LENGTH, RCS_CTA_URL_TYPE, DYNAMIC_URL_SUFFIX,
9
+ } from '../constants';
10
+ import { RCS_BUTTON_TYPES, HOST_ICS } from '../../../v2Containers/Rcs/constants';
9
11
 
10
12
  const updateHandler = jest.fn();
11
13
  const deleteHandler = jest.fn();
@@ -13,6 +15,7 @@ const initializeComponent = (
13
15
  data,
14
16
  isEditFlow = false,
15
17
  maxButtons = 3,
18
+ host = '',
16
19
  ) => {
17
20
  // Normalize legacy test data shape to match component props
18
21
  const normalizeType = (legacyType) => {
@@ -26,6 +29,7 @@ const initializeComponent = (
26
29
  text: d.displayText ?? d.text ?? '',
27
30
  phoneNumber: d.phoneNumber ?? '',
28
31
  url: d.url ?? '',
32
+ urlType: d.urlType,
29
33
  postback: d.postback ?? '',
30
34
  isSaved: d.isSaved ?? false,
31
35
  }));
@@ -38,6 +42,7 @@ const initializeComponent = (
38
42
  isEditFlow={isEditFlow}
39
43
  maxButtons={maxButtons}
40
44
  isFullMode={true}
45
+ host={host}
41
46
  />,
42
47
  );
43
48
  };
@@ -60,7 +65,7 @@ describe('CapActionButton', () => {
60
65
  isSaved: false,
61
66
  },
62
67
  ]);
63
- expect(screen.getByText('Type of action')).toBeInTheDocument();
68
+ expect(screen.getByText('Button type')).toBeInTheDocument();
64
69
  expect(screen.getByText('Button text')).toBeInTheDocument();
65
70
  expect(screen.getAllByText('Phone number')[1]).toBeInTheDocument();
66
71
  expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
@@ -123,7 +128,7 @@ describe('CapActionButton', () => {
123
128
  id: 1,
124
129
  },
125
130
  };
126
- const urlInput = await screen.getByPlaceholderText('Enter website URL');
131
+ const urlInput = await screen.getByPlaceholderText('Enter URL');
127
132
  fireEvent.change(urlInput, urlEvent);
128
133
  expect(updateHandler).toHaveBeenCalledWith(
129
134
  {
@@ -163,6 +168,27 @@ describe('CapActionButton', () => {
163
168
  );
164
169
  });
165
170
 
171
+ it('should show delete for quick reply index 0 for non-ICS hosts but hide it for ICS (stop button)', () => {
172
+ const button = {
173
+ index: 0,
174
+ type: RCS_BUTTON_TYPES.QUICK_REPLY,
175
+ text: 'Reply',
176
+ phoneNumber: '',
177
+ url: '',
178
+ postback: 'Reply',
179
+ isSaved: false,
180
+ };
181
+
182
+ // Non-ICS (Infobip/others): delete should be available
183
+ const { unmount } = initializeComponent([button], false, 3, 'rcsinfobipbulk');
184
+ expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument();
185
+ unmount();
186
+
187
+ // ICS: index 0 is reserved; delete should be hidden
188
+ initializeComponent([button], false, 3, HOST_ICS);
189
+ expect(screen.queryByRole('button', { name: /delete/i })).not.toBeInTheDocument();
190
+ });
191
+
166
192
  it('should respect character length limits', async () => {
167
193
  const initialData = {
168
194
  index: 0,
@@ -201,7 +227,7 @@ describe('CapActionButton', () => {
201
227
  isSaved: false,
202
228
  };
203
229
  initializeComponent([initialData]);
204
- const urlInput = await screen.getByPlaceholderText('Enter website URL');
230
+ const urlInput = await screen.getByPlaceholderText('Enter URL');
205
231
  const longUrl = 'https://example.com/' + 'a'.repeat(URL_MAX_LENGTH + 1);
206
232
  fireEvent.change(urlInput, { target: { value: longUrl, id: 0 } });
207
233
  expect(urlInput.value.length).toBeLessThanOrEqual(URL_MAX_LENGTH);
@@ -248,7 +274,8 @@ describe('CapActionButton', () => {
248
274
  { index: 0, ctaType: RCS_BUTTON_TYPES.QUICK_REPLY, displayText: 'stop', phoneNumber: '', url: '', postback: 'stop', isSaved: true },
249
275
  { index: 1, ctaType: RCS_BUTTON_TYPES.QUICK_REPLY, displayText: 'Saved', phoneNumber: '', url: '', postback: 'Saved', isSaved: true },
250
276
  ];
251
- const { container } = initializeComponent(suggestions);
277
+ // Use ICS host so index 0 (STOP) stays non-deletable and we only get delete icon for index 1
278
+ const { container } = initializeComponent(suggestions, false, 3, HOST_ICS);
252
279
  const deleteIcons = container.querySelectorAll('.rcs-saved-cta-delete-icon');
253
280
  expect(deleteIcons.length).toBeGreaterThan(0);
254
281
  fireEvent.click(deleteIcons[0]);
@@ -306,7 +333,8 @@ describe('CapActionButton', () => {
306
333
  postback: 'stop',
307
334
  isSaved: true,
308
335
  };
309
- const { container } = initializeComponent([stopButton]);
336
+ // Only ICS treats index 0 as a mandatory STOP quick reply
337
+ const { container } = initializeComponent([stopButton], false, 3, HOST_ICS);
310
338
  expect(container.querySelector('.rcs-saved-cta-delete-icon')).not.toBeInTheDocument();
311
339
  });
312
340
 
@@ -427,7 +455,7 @@ describe('CapActionButton', () => {
427
455
  isSaved: false,
428
456
  };
429
457
  initializeComponent([button]);
430
- expect(screen.getByPlaceholderText(/enter website url/i)).toBeInTheDocument();
458
+ expect(screen.getByPlaceholderText(/enter url/i)).toBeInTheDocument();
431
459
  });
432
460
 
433
461
  it('should update both displayText and postback when button text changes (updateDisplayAndPostback)', () => {
@@ -507,7 +535,7 @@ describe('CapActionButton', () => {
507
535
  isSaved: false,
508
536
  };
509
537
  initializeComponent([initial]);
510
- const urlInput = screen.getByPlaceholderText('Enter website URL');
538
+ const urlInput = screen.getByPlaceholderText('Enter URL');
511
539
  fireEvent.change(urlInput, { target: { value: 'http://localhost:3030/creatives/ui/v2', id: 1 } });
512
540
  expect(screen.getByText(/url is not valid/i)).toBeInTheDocument();
513
541
  });
@@ -526,6 +554,34 @@ describe('CapActionButton', () => {
526
554
  expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
527
555
  });
528
556
 
557
+ it('should disable save for a dynamic-URL CTA still holding the unresolved {{1}} placeholder', () => {
558
+ const initial = {
559
+ index: 0,
560
+ type: RCS_BUTTON_TYPES.CTA,
561
+ text: 'Visit here',
562
+ url: `https://example.com${DYNAMIC_URL_SUFFIX}`,
563
+ urlType: RCS_CTA_URL_TYPE.DYNAMIC,
564
+ postback: 'Visit here',
565
+ isSaved: false,
566
+ };
567
+ initializeComponent([initial]);
568
+ expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
569
+ });
570
+
571
+ it('should enable save for a dynamic-URL CTA once the placeholder has been replaced with a real tag', () => {
572
+ const initial = {
573
+ index: 0,
574
+ type: RCS_BUTTON_TYPES.CTA,
575
+ text: 'Visit here',
576
+ url: 'https://example.com{{first_name}}',
577
+ urlType: RCS_CTA_URL_TYPE.DYNAMIC,
578
+ postback: 'Visit here',
579
+ isSaved: false,
580
+ };
581
+ initializeComponent([initial]);
582
+ expect(screen.getByRole('button', { name: /save/i })).not.toBeDisabled();
583
+ });
584
+
529
585
  it('should render edit and delete icons in edit mode (renderedContent)', () => {
530
586
  const button = {
531
587
  index: 1,
@@ -607,12 +663,12 @@ describe('CapActionButton', () => {
607
663
  );
608
664
  };
609
665
  render(<StatefulWrapper />);
610
- let urlInput = screen.getByPlaceholderText('Enter website URL');
666
+ let urlInput = screen.getByPlaceholderText('Enter URL');
611
667
  // Enter invalid URL
612
668
  fireEvent.change(urlInput, { target: { value: 'badurl', id: 0 } });
613
669
  expect(await screen.findByText(/url is not valid/i)).toBeInTheDocument();
614
670
  // Re-grab input after re-render and enter valid URL
615
- urlInput = screen.getByPlaceholderText('Enter website URL');
671
+ urlInput = screen.getByPlaceholderText('Enter URL');
616
672
  fireEvent.change(urlInput, { target: { value: 'https://good.com', id: 0 } });
617
673
  await waitFor(() => expect(screen.queryByText(/url is not valid/i)).not.toBeInTheDocument());
618
674
  });
@@ -867,15 +923,14 @@ describe('CapActionButton function logic', () => {
867
923
  expect(renderCtaOptions(label, tooltipLabel, false)).toBe('Test');
868
924
  });
869
925
 
870
- it('renderLength returns correct CapHeading', () => {
871
- const CapHeading = ({ type, className, children }) => <div className={className}>{children}</div>;
872
- const formatMessage = (msg, values) => `${values.currentLength}/${values.maxLength}`;
873
- const renderLength = (len, max) => (
874
- <CapHeading type="label1" className="rcs-render-btn-length">
875
- {formatMessage({}, { currentLength: len, maxLength: max })}
876
- </CapHeading>
926
+ it('inner char count uses current/max format', () => {
927
+ const formatMessage = (msg, values) => `${values.current}/${values.max}`;
928
+ const renderInnerCharCount = (len, max) => (
929
+ <span className="rcs-cta-inner-char-count">
930
+ {formatMessage({}, { current: len, max })}
931
+ </span>
877
932
  );
878
- const result = renderLength(5, 25);
933
+ const result = renderInnerCharCount(5, 25);
879
934
  expect(result.props.children).toBe('5/25');
880
935
  });
881
936
  });
@@ -5,7 +5,7 @@
5
5
  *
6
6
  */
7
7
 
8
- import React, { useState, useEffect, useCallback } from 'react';
8
+ import React, { useState, useEffect, useCallback, useMemo } from 'react';
9
9
  import PropTypes from "prop-types";
10
10
 
11
11
  import LabelHOC from '@capillarytech/cap-ui-library/assets/HOCs/ComponentWithLabelHOC';
@@ -92,7 +92,7 @@ function CapImageUpload(props) {
92
92
  [imageSrc],
93
93
  );
94
94
 
95
- const WithLabel = LabelHOC(ImageComponent);
95
+ const WithLabel = useMemo(() => LabelHOC(ImageComponent), [ImageComponent]);
96
96
 
97
97
  const uploadImages = useCallback((e, { files }) => {
98
98
  if (e) {
@@ -346,6 +346,8 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
346
346
  render() {
347
347
  const {
348
348
  hidePopover = false, intl = {}, moduleFilterEnabled, label, modalProps, channel, fetchingSchemaError = false, getPopupContainer,
349
+ overlayStyle,
350
+ overlayClassName,
349
351
  } = this.props;
350
352
  const {formatMessage} = intl;
351
353
  const {
@@ -440,6 +442,9 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
440
442
  classNames={{
441
443
  container: 'tag-list-popover-container'
442
444
  }}
445
+ overlayStyle={overlayStyle}
446
+ overlayClassName={overlayClassName}
447
+ getPopupContainer={getPopupContainer}
443
448
  >
444
449
  <CapTooltip
445
450
  title={
@@ -511,6 +516,10 @@ CapTagList.propTypes = {
511
516
  disableTooltipMsg: PropTypes.string,
512
517
  fetchingSchemaError: PropTypes.bool,
513
518
  popoverPlacement: PropTypes.string,
519
+ overlayStyle: PropTypes.object,
520
+ overlayClassName: PropTypes.string,
521
+ /** e.g. () => document.body — avoids overflow/stacking issues inside slideboxes */
522
+ getPopupContainer: PropTypes.func,
514
523
  };
515
524
 
516
525
  CapTagList.defaultValue = {