@capillarytech/creatives-library 9.0.15-alpha.0 → 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.
- package/constants/unified.js +29 -0
- package/global-styles.js +1 -1
- package/package.json +1 -1
- package/services/tests/api.test.js +35 -20
- package/utils/commonUtils.js +19 -1
- package/utils/rcsPayloadUtils.js +92 -0
- package/utils/templateVarUtils.js +201 -0
- package/utils/tests/rcsPayloadUtils.test.js +226 -0
- package/utils/tests/templateVarUtils.test.js +204 -0
- package/v2Components/CapActionButton/constants.js +8 -1
- package/v2Components/CapActionButton/index.js +202 -144
- package/v2Components/CapActionButton/index.scss +232 -14
- package/v2Components/CapActionButton/messages.js +19 -3
- package/v2Components/CapActionButton/tests/index.test.js +42 -18
- package/v2Components/CapImageUpload/index.js +2 -2
- package/v2Components/CapTagList/index.js +10 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -48
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +60 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +202 -22
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +381 -80
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +155 -8
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
- package/v2Components/CommonTestAndPreview/constants.js +38 -2
- package/v2Components/CommonTestAndPreview/index.js +761 -222
- package/v2Components/CommonTestAndPreview/messages.js +45 -3
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/v2Components/CommonTestAndPreview/sagas.js +25 -6
- package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +1 -0
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -267
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
- package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +133 -4
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
- package/v2Components/CommonTestAndPreview/utils.js +84 -0
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
- package/v2Components/SmsFallback/constants.js +94 -0
- package/v2Components/SmsFallback/index.js +958 -0
- package/v2Components/SmsFallback/index.scss +266 -0
- package/v2Components/SmsFallback/messages.js +78 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +120 -0
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +208 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +45 -25
- package/v2Components/TemplatePreview/constants.js +2 -0
- package/v2Components/TemplatePreview/index.js +147 -32
- package/v2Components/TemplatePreview/tests/index.test.js +142 -0
- package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
- package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
- package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
- package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
- package/v2Components/VarSegmentMessageEditor/index.js +125 -0
- package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +4 -1
- package/v2Containers/CommunicationFlow/index.js +3 -3
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +4 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +17 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +37 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
- package/v2Containers/CreativesContainer/constants.js +11 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
- package/v2Containers/CreativesContainer/index.js +323 -104
- package/v2Containers/CreativesContainer/index.scss +83 -1
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +79 -34
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +333 -90
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
- package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
- package/v2Containers/Rcs/constants.js +131 -11
- package/v2Containers/Rcs/index.js +2685 -832
- package/v2Containers/Rcs/index.scss +443 -8
- package/v2Containers/Rcs/messages.js +45 -6
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +227 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +74176 -39409
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +10 -74
- package/v2Containers/Rcs/tests/index.test.js +151 -40
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
- package/v2Containers/Rcs/tests/utils.test.js +643 -30
- package/v2Containers/Rcs/utils.js +466 -12
- package/v2Containers/Sms/Create/index.js +115 -48
- package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/v2Containers/SmsTrai/Create/index.js +9 -4
- package/v2Containers/SmsTrai/Edit/constants.js +2 -0
- package/v2Containers/SmsTrai/Edit/index.js +678 -169
- package/v2Containers/SmsTrai/Edit/index.scss +126 -0
- package/v2Containers/SmsTrai/Edit/messages.js +14 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +5615 -3014
- package/v2Containers/SmsWrapper/index.js +37 -8
- package/v2Containers/TagList/index.js +6 -0
- package/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/v2Containers/Templates/_templates.scss +196 -12
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +131 -59
- package/v2Containers/Templates/sagas.js +57 -13
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1060 -1015
- package/v2Containers/Templates/tests/sagas.test.js +199 -16
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
- package/v2Containers/TemplatesV2/index.js +86 -23
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
- package/v2Containers/Whatsapp/index.js +3 -20
- 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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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,189 @@
|
|
|
37
73
|
font-weight: 500;
|
|
38
74
|
}
|
|
39
75
|
|
|
40
|
-
.rcs-saved-cta-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
47
|
-
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Button text / URL: count via CapInput `suffix` (antd affix, right-aligned inside field).
|
|
90
|
+
// Phone: react-phone-input-2 — overlay count + padding on .form-control
|
|
91
|
+
// Selector needs `.cap-heading.h5` too — CapHeading's own `.cap-heading.h5` rule (2 classes)
|
|
92
|
+
// otherwise beats a single-class `.rcs-cta-inner-char-count` selector and forces dark text,
|
|
93
|
+
// regardless of source order (light-grey placeholder look per Figma).
|
|
94
|
+
.cap-heading.h5.rcs-cta-inner-char-count {
|
|
95
|
+
font-size: 0.75rem;
|
|
96
|
+
line-height: 1.25rem;
|
|
97
|
+
color: $FONT_COLOR_03;
|
|
98
|
+
white-space: nowrap;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Button text: match the phone field's full-width treatment (Figma reference).
|
|
102
|
+
// `className` on CapInput lands on componentWithLabelHOC's OUTER wrapper div
|
|
103
|
+
// (confirmed via rendered DOM), not on the `.ant-input-affix-wrapper` itself —
|
|
104
|
+
// force the wrapper full-width directly rather than relying on descendant rules.
|
|
105
|
+
.rcs-cta-button-text {
|
|
106
|
+
width: 100%;
|
|
107
|
+
|
|
108
|
+
.ant-input-affix-wrapper {
|
|
109
|
+
width: 100%;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.rcs-cta-input-with-inner-count {
|
|
114
|
+
position: relative;
|
|
115
|
+
width: 100%;
|
|
116
|
+
|
|
117
|
+
&--phone .rcs-cta-inner-char-count {
|
|
118
|
+
position: absolute;
|
|
119
|
+
right: 0.6875rem;
|
|
120
|
+
top: 50%;
|
|
121
|
+
transform: translateY(-50%);
|
|
122
|
+
z-index: 2;
|
|
123
|
+
pointer-events: none;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// RCS phone field: Figma — one light border, 4px radius, ~antd Input large height, full width, count inside right
|
|
128
|
+
.rcs-button-cta-create-container .rcs-cta-input-with-inner-count--phone {
|
|
129
|
+
.react-tel-input.rcs-cta-phone-input {
|
|
130
|
+
width: 100%;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.rcs-cta-phone-input .form-control {
|
|
134
|
+
width: 100% !important;
|
|
135
|
+
max-width: none;
|
|
136
|
+
height: 2.5rem;
|
|
137
|
+
min-height: 2.5rem;
|
|
138
|
+
padding-top: 0;
|
|
139
|
+
padding-bottom: 0;
|
|
140
|
+
padding-left: 3rem;
|
|
141
|
+
padding-right: 3.5rem;
|
|
142
|
+
margin: 0;
|
|
143
|
+
font-size: 0.875rem;
|
|
144
|
+
line-height: 2.5rem;
|
|
145
|
+
color: $CAP_G01;
|
|
146
|
+
background: $CAP_WHITE;
|
|
147
|
+
border: 1px solid $CAP_G06;
|
|
148
|
+
border-radius: 0.25rem;
|
|
149
|
+
box-shadow: none;
|
|
150
|
+
|
|
151
|
+
&::placeholder {
|
|
152
|
+
color: $CAP_G05;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
&:hover {
|
|
156
|
+
border-color: $CAP_G11;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
&:focus {
|
|
160
|
+
border-color: $CAP_G01;
|
|
161
|
+
box-shadow: none;
|
|
162
|
+
outline: none;
|
|
48
163
|
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// The flag-dropdown sits on top of .form-control's left edge, so hovering the
|
|
167
|
+
// flag itself doesn't trigger `.form-control:hover` — bind the border-color
|
|
168
|
+
// change to the whole wrapper so the border reacts consistently everywhere.
|
|
169
|
+
.react-tel-input.rcs-cta-phone-input:hover .form-control {
|
|
170
|
+
border-color: $CAP_G11;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.react-tel-input.rcs-cta-phone-input:focus-within .form-control {
|
|
174
|
+
border-color: $CAP_G01;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.rcs-cta-phone-input .flag-dropdown {
|
|
178
|
+
// Raw px, not rem: must exactly match .form-control's 1px border width.
|
|
179
|
+
// At this app's 14px root font-size, 0.0625rem resolved to 0.875px — the
|
|
180
|
+
// sub-pixel gap left the outer border looking "cut off" right where the
|
|
181
|
+
// flag-dropdown's opaque background sat, most visible on hover when the
|
|
182
|
+
// border color changed and made the gap's anti-aliasing stand out.
|
|
183
|
+
top: 1px;
|
|
184
|
+
bottom: 1px;
|
|
185
|
+
left: 1px;
|
|
186
|
+
padding: 0;
|
|
187
|
+
background: $CAP_WHITE;
|
|
188
|
+
border: none;
|
|
189
|
+
border-radius: 0.1875rem 0 0 0.1875rem; // inset from outer 4px radius
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.rcs-cta-phone-input .flag-dropdown.open {
|
|
193
|
+
border-radius: 0.1875rem 0 0 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.rcs-cta-phone-input .selected-flag {
|
|
197
|
+
width: 2.75rem;
|
|
198
|
+
padding: 0 0 0 0.5rem;
|
|
199
|
+
background: transparent;
|
|
200
|
+
border-radius: 0.1875rem 0 0 0.1875rem;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.rcs-cta-phone-input .selected-flag:hover,
|
|
204
|
+
.rcs-cta-phone-input .selected-flag:focus {
|
|
205
|
+
background: $CAP_G09;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.rcs-cta-phone-input .selected-flag .arrow {
|
|
209
|
+
left: 1.375rem;
|
|
210
|
+
border-top-color: $CAP_G04;
|
|
211
|
+
}
|
|
49
212
|
}
|
|
50
213
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
214
|
+
// URL CTA: URL type (narrow) + URL field (~1:3) per Figma
|
|
215
|
+
.rcs-cta-url-fields-row {
|
|
216
|
+
.rcs-cta-url-type-col .ant-select {
|
|
217
|
+
width: 100%;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// RCS “create CTA” card: 0.5rem padding + gaps (Figma-tight; fixes excess space above first label)
|
|
222
|
+
.rcs-button-cta-create-container.cap-row-v2 {
|
|
223
|
+
display: flex;
|
|
224
|
+
flex-direction: column;
|
|
225
|
+
// Cross-axis (=width, since this is a column-flex row) defaults to `align-items: flex-start`
|
|
226
|
+
// via antd's `.ant-row-top`, which lets child Cols shrink to content instead of filling the
|
|
227
|
+
// row — same issue CapRow's own `.cap-row-vertical` guards against. Force stretch here too,
|
|
228
|
+
// otherwise fields with no explicit width of their own (e.g. Button text) render too narrow.
|
|
229
|
+
align-items: stretch;
|
|
230
|
+
flex-wrap: nowrap;
|
|
231
|
+
width: 100%;
|
|
232
|
+
border: solid 1px $CAP_G06;
|
|
233
|
+
padding: 1.25rem;
|
|
234
|
+
border-radius: 0.25rem; // 4px — match .cap-rcs-saved-cta
|
|
235
|
+
|
|
236
|
+
// Stack “Button type” (radios) above “Button text”
|
|
237
|
+
.rcs-button-cta-create.cap-row-v2 {
|
|
238
|
+
display: flex;
|
|
239
|
+
flex-direction: column;
|
|
240
|
+
align-items: stretch;
|
|
241
|
+
flex-wrap: nowrap;
|
|
242
|
+
margin: 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Space after each field label (CapHeading margins are often zeroed by the library)
|
|
246
|
+
.cta-label {
|
|
247
|
+
margin-bottom: 0;
|
|
248
|
+
margin-top: 0.75rem;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.cta-label + * {
|
|
252
|
+
margin-top: 0.5rem;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// “Button type” heading has no .cta-label — keep same label→control gap as other fields
|
|
256
|
+
.rcs-button-cta-create > .ant-col:first-of-type .cap-rcs-cta-type-radio {
|
|
257
|
+
margin-top: 0.5rem;
|
|
258
|
+
}
|
|
54
259
|
}
|
|
55
260
|
|
|
56
261
|
.disabled {
|
|
@@ -65,3 +270,16 @@
|
|
|
65
270
|
.button-disabled-tooltip-wrapper {
|
|
66
271
|
display: inline-block;
|
|
67
272
|
}
|
|
273
|
+
|
|
274
|
+
// Space above Save/Delete row: kept on this stable outer wrapper (not on
|
|
275
|
+
// .button-disabled-tooltip-wrapper) since that inner CapRow's own display
|
|
276
|
+
// (flex vs inline-block) flips depending on whether CapTooltip renders its
|
|
277
|
+
// extra trigger span (title set vs empty), which made the gap disappear
|
|
278
|
+
// whenever the Save button was enabled.
|
|
279
|
+
.rcs-cta-save-delete-btn {
|
|
280
|
+
margin-top: 1.25rem;
|
|
281
|
+
|
|
282
|
+
.rcs-cta-delete-btn {
|
|
283
|
+
margin-left: 0.75rem;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -12,7 +12,7 @@ export default defineMessages({
|
|
|
12
12
|
},
|
|
13
13
|
ctaQr: {
|
|
14
14
|
id: `${prefix}.ctaQr`,
|
|
15
|
-
defaultMessage: 'Quick
|
|
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: '
|
|
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
|
|
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`,
|
|
@@ -5,7 +5,7 @@ import { render, screen, fireEvent } from '../../../utils/test-utils';
|
|
|
5
5
|
import { waitFor } from '@testing-library/react';
|
|
6
6
|
import { CapActionButton } from '../index';
|
|
7
7
|
import { BTN_MAX_LENGTH, PHONE_NUMBER_MAX_LENGTH, URL_MAX_LENGTH } from '../constants';
|
|
8
|
-
import { RCS_BUTTON_TYPES } from '../../../v2Containers/Rcs/constants';
|
|
8
|
+
import { RCS_BUTTON_TYPES, HOST_ICS } from '../../../v2Containers/Rcs/constants';
|
|
9
9
|
|
|
10
10
|
const updateHandler = jest.fn();
|
|
11
11
|
const deleteHandler = jest.fn();
|
|
@@ -13,6 +13,7 @@ const initializeComponent = (
|
|
|
13
13
|
data,
|
|
14
14
|
isEditFlow = false,
|
|
15
15
|
maxButtons = 3,
|
|
16
|
+
host = '',
|
|
16
17
|
) => {
|
|
17
18
|
// Normalize legacy test data shape to match component props
|
|
18
19
|
const normalizeType = (legacyType) => {
|
|
@@ -38,6 +39,7 @@ const initializeComponent = (
|
|
|
38
39
|
isEditFlow={isEditFlow}
|
|
39
40
|
maxButtons={maxButtons}
|
|
40
41
|
isFullMode={true}
|
|
42
|
+
host={host}
|
|
41
43
|
/>,
|
|
42
44
|
);
|
|
43
45
|
};
|
|
@@ -60,7 +62,7 @@ describe('CapActionButton', () => {
|
|
|
60
62
|
isSaved: false,
|
|
61
63
|
},
|
|
62
64
|
]);
|
|
63
|
-
expect(screen.getByText('
|
|
65
|
+
expect(screen.getByText('Button type')).toBeInTheDocument();
|
|
64
66
|
expect(screen.getByText('Button text')).toBeInTheDocument();
|
|
65
67
|
expect(screen.getAllByText('Phone number')[1]).toBeInTheDocument();
|
|
66
68
|
expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
|
|
@@ -123,7 +125,7 @@ describe('CapActionButton', () => {
|
|
|
123
125
|
id: 1,
|
|
124
126
|
},
|
|
125
127
|
};
|
|
126
|
-
const urlInput = await screen.getByPlaceholderText('Enter
|
|
128
|
+
const urlInput = await screen.getByPlaceholderText('Enter URL');
|
|
127
129
|
fireEvent.change(urlInput, urlEvent);
|
|
128
130
|
expect(updateHandler).toHaveBeenCalledWith(
|
|
129
131
|
{
|
|
@@ -163,6 +165,27 @@ describe('CapActionButton', () => {
|
|
|
163
165
|
);
|
|
164
166
|
});
|
|
165
167
|
|
|
168
|
+
it('should show delete for quick reply index 0 for non-ICS hosts but hide it for ICS (stop button)', () => {
|
|
169
|
+
const button = {
|
|
170
|
+
index: 0,
|
|
171
|
+
type: RCS_BUTTON_TYPES.QUICK_REPLY,
|
|
172
|
+
text: 'Reply',
|
|
173
|
+
phoneNumber: '',
|
|
174
|
+
url: '',
|
|
175
|
+
postback: 'Reply',
|
|
176
|
+
isSaved: false,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Non-ICS (Infobip/others): delete should be available
|
|
180
|
+
const { unmount } = initializeComponent([button], false, 3, 'rcsinfobipbulk');
|
|
181
|
+
expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument();
|
|
182
|
+
unmount();
|
|
183
|
+
|
|
184
|
+
// ICS: index 0 is reserved; delete should be hidden
|
|
185
|
+
initializeComponent([button], false, 3, HOST_ICS);
|
|
186
|
+
expect(screen.queryByRole('button', { name: /delete/i })).not.toBeInTheDocument();
|
|
187
|
+
});
|
|
188
|
+
|
|
166
189
|
it('should respect character length limits', async () => {
|
|
167
190
|
const initialData = {
|
|
168
191
|
index: 0,
|
|
@@ -201,7 +224,7 @@ describe('CapActionButton', () => {
|
|
|
201
224
|
isSaved: false,
|
|
202
225
|
};
|
|
203
226
|
initializeComponent([initialData]);
|
|
204
|
-
const urlInput = await screen.getByPlaceholderText('Enter
|
|
227
|
+
const urlInput = await screen.getByPlaceholderText('Enter URL');
|
|
205
228
|
const longUrl = 'https://example.com/' + 'a'.repeat(URL_MAX_LENGTH + 1);
|
|
206
229
|
fireEvent.change(urlInput, { target: { value: longUrl, id: 0 } });
|
|
207
230
|
expect(urlInput.value.length).toBeLessThanOrEqual(URL_MAX_LENGTH);
|
|
@@ -248,7 +271,8 @@ describe('CapActionButton', () => {
|
|
|
248
271
|
{ index: 0, ctaType: RCS_BUTTON_TYPES.QUICK_REPLY, displayText: 'stop', phoneNumber: '', url: '', postback: 'stop', isSaved: true },
|
|
249
272
|
{ index: 1, ctaType: RCS_BUTTON_TYPES.QUICK_REPLY, displayText: 'Saved', phoneNumber: '', url: '', postback: 'Saved', isSaved: true },
|
|
250
273
|
];
|
|
251
|
-
|
|
274
|
+
// Use ICS host so index 0 (STOP) stays non-deletable and we only get delete icon for index 1
|
|
275
|
+
const { container } = initializeComponent(suggestions, false, 3, HOST_ICS);
|
|
252
276
|
const deleteIcons = container.querySelectorAll('.rcs-saved-cta-delete-icon');
|
|
253
277
|
expect(deleteIcons.length).toBeGreaterThan(0);
|
|
254
278
|
fireEvent.click(deleteIcons[0]);
|
|
@@ -306,7 +330,8 @@ describe('CapActionButton', () => {
|
|
|
306
330
|
postback: 'stop',
|
|
307
331
|
isSaved: true,
|
|
308
332
|
};
|
|
309
|
-
|
|
333
|
+
// Only ICS treats index 0 as a mandatory STOP quick reply
|
|
334
|
+
const { container } = initializeComponent([stopButton], false, 3, HOST_ICS);
|
|
310
335
|
expect(container.querySelector('.rcs-saved-cta-delete-icon')).not.toBeInTheDocument();
|
|
311
336
|
});
|
|
312
337
|
|
|
@@ -427,7 +452,7 @@ describe('CapActionButton', () => {
|
|
|
427
452
|
isSaved: false,
|
|
428
453
|
};
|
|
429
454
|
initializeComponent([button]);
|
|
430
|
-
expect(screen.getByPlaceholderText(/enter
|
|
455
|
+
expect(screen.getByPlaceholderText(/enter url/i)).toBeInTheDocument();
|
|
431
456
|
});
|
|
432
457
|
|
|
433
458
|
it('should update both displayText and postback when button text changes (updateDisplayAndPostback)', () => {
|
|
@@ -507,7 +532,7 @@ describe('CapActionButton', () => {
|
|
|
507
532
|
isSaved: false,
|
|
508
533
|
};
|
|
509
534
|
initializeComponent([initial]);
|
|
510
|
-
const urlInput = screen.getByPlaceholderText('Enter
|
|
535
|
+
const urlInput = screen.getByPlaceholderText('Enter URL');
|
|
511
536
|
fireEvent.change(urlInput, { target: { value: 'http://localhost:3030/creatives/ui/v2', id: 1 } });
|
|
512
537
|
expect(screen.getByText(/url is not valid/i)).toBeInTheDocument();
|
|
513
538
|
});
|
|
@@ -607,12 +632,12 @@ describe('CapActionButton', () => {
|
|
|
607
632
|
);
|
|
608
633
|
};
|
|
609
634
|
render(<StatefulWrapper />);
|
|
610
|
-
let urlInput = screen.getByPlaceholderText('Enter
|
|
635
|
+
let urlInput = screen.getByPlaceholderText('Enter URL');
|
|
611
636
|
// Enter invalid URL
|
|
612
637
|
fireEvent.change(urlInput, { target: { value: 'badurl', id: 0 } });
|
|
613
638
|
expect(await screen.findByText(/url is not valid/i)).toBeInTheDocument();
|
|
614
639
|
// Re-grab input after re-render and enter valid URL
|
|
615
|
-
urlInput = screen.getByPlaceholderText('Enter
|
|
640
|
+
urlInput = screen.getByPlaceholderText('Enter URL');
|
|
616
641
|
fireEvent.change(urlInput, { target: { value: 'https://good.com', id: 0 } });
|
|
617
642
|
await waitFor(() => expect(screen.queryByText(/url is not valid/i)).not.toBeInTheDocument());
|
|
618
643
|
});
|
|
@@ -867,15 +892,14 @@ describe('CapActionButton function logic', () => {
|
|
|
867
892
|
expect(renderCtaOptions(label, tooltipLabel, false)).toBe('Test');
|
|
868
893
|
});
|
|
869
894
|
|
|
870
|
-
it('
|
|
871
|
-
const
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
</CapHeading>
|
|
895
|
+
it('inner char count uses current/max format', () => {
|
|
896
|
+
const formatMessage = (msg, values) => `${values.current}/${values.max}`;
|
|
897
|
+
const renderInnerCharCount = (len, max) => (
|
|
898
|
+
<span className="rcs-cta-inner-char-count">
|
|
899
|
+
{formatMessage({}, { current: len, max })}
|
|
900
|
+
</span>
|
|
877
901
|
);
|
|
878
|
-
const result =
|
|
902
|
+
const result = renderInnerCharCount(5, 25);
|
|
879
903
|
expect(result.props.children).toBe('5/25');
|
|
880
904
|
});
|
|
881
905
|
});
|
|
@@ -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,9 @@ 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,
|
|
349
|
+
overlayStyle,
|
|
350
|
+
overlayClassName,
|
|
351
|
+
getPopupContainer,
|
|
349
352
|
} = this.props;
|
|
350
353
|
const {formatMessage} = intl;
|
|
351
354
|
const {
|
|
@@ -439,6 +442,9 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
439
442
|
classNames={{
|
|
440
443
|
container: 'tag-list-popover-container'
|
|
441
444
|
}}
|
|
445
|
+
overlayStyle={overlayStyle}
|
|
446
|
+
overlayClassName={overlayClassName}
|
|
447
|
+
getPopupContainer={getPopupContainer}
|
|
442
448
|
>
|
|
443
449
|
<CapTooltip
|
|
444
450
|
title={
|
|
@@ -510,6 +516,10 @@ CapTagList.propTypes = {
|
|
|
510
516
|
disableTooltipMsg: PropTypes.string,
|
|
511
517
|
fetchingSchemaError: PropTypes.bool,
|
|
512
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,
|
|
513
523
|
};
|
|
514
524
|
|
|
515
525
|
CapTagList.defaultValue = {
|
|
@@ -8,6 +8,8 @@ import CapButton from '@capillarytech/cap-ui-library/CapButton';
|
|
|
8
8
|
import CapInput from '@capillarytech/cap-ui-library/CapInput';
|
|
9
9
|
import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
|
|
10
10
|
import messages from './messages';
|
|
11
|
+
import { CUSTOM_VALUES_EDITOR_SECTION_FALLBACK_KEY } from './constants';
|
|
12
|
+
import { getPersonalizationTagColumnLabel } from './utils';
|
|
11
13
|
|
|
12
14
|
const CustomValuesEditor = ({
|
|
13
15
|
isExtractingTags,
|
|
@@ -16,9 +18,7 @@ const CustomValuesEditor = ({
|
|
|
16
18
|
setShowJSON,
|
|
17
19
|
customValues,
|
|
18
20
|
handleJSONTextChange,
|
|
19
|
-
|
|
20
|
-
requiredTags,
|
|
21
|
-
optionalTags,
|
|
21
|
+
sections,
|
|
22
22
|
handleCustomValueChange,
|
|
23
23
|
handleDiscardCustomValues,
|
|
24
24
|
handleUpdatePreview,
|
|
@@ -77,51 +77,70 @@ const CustomValuesEditor = ({
|
|
|
77
77
|
</CapRow>
|
|
78
78
|
) : (
|
|
79
79
|
<>
|
|
80
|
-
{
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
{(sections || []).filter((tagsSection) =>
|
|
81
|
+
(tagsSection?.requiredTags?.length || 0) + (tagsSection?.optionalTags?.length || 0) > 0).map((section) => (
|
|
82
|
+
<React.Fragment key={section.key || section.title?.id || section.title || CUSTOM_VALUES_EDITOR_SECTION_FALLBACK_KEY}>
|
|
83
|
+
{section.title ? (
|
|
84
|
+
<CapLabel type="label2" className="tags-section-title">
|
|
85
|
+
{typeof section.title === 'string' ? section.title : <FormattedMessage {...section.title} />}
|
|
85
86
|
</CapLabel>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<
|
|
93
|
-
{
|
|
94
|
-
</
|
|
95
|
-
<CapRow className="tag-input">
|
|
96
|
-
<CapInput
|
|
97
|
-
type="text"
|
|
98
|
-
isRequired
|
|
99
|
-
className="tag-input-field"
|
|
100
|
-
value={customValues[tag.fullPath] || ''}
|
|
101
|
-
onChange={(e) => handleCustomValueChange(tag.fullPath, e.target.value)}
|
|
102
|
-
placeholder={formatMessage(messages.enterValue)}
|
|
103
|
-
size="small"
|
|
104
|
-
/>
|
|
105
|
-
</CapRow>
|
|
87
|
+
) : null}
|
|
88
|
+
<CapRow className="values-table">
|
|
89
|
+
<CapRow type="flex" className="table-header">
|
|
90
|
+
<CapLabel type="label31" className="header-cell">
|
|
91
|
+
<FormattedMessage {...messages.personalizationTags} />
|
|
92
|
+
</CapLabel>
|
|
93
|
+
<CapLabel type="label31" className="header-cell">
|
|
94
|
+
<FormattedMessage {...messages.customValues} />
|
|
95
|
+
</CapLabel>
|
|
106
96
|
</CapRow>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<CapRow className="
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
className="tag-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
97
|
+
{(section?.requiredTags || []).map((tag, tagIndex) => {
|
|
98
|
+
const personalizationTagColumnText = getPersonalizationTagColumnLabel(tag);
|
|
99
|
+
const tagKey = tag?.fullPath ?? `required-${tagIndex}`;
|
|
100
|
+
return (
|
|
101
|
+
<CapRow key={tagKey} type="flex" noWrap className="value-row">
|
|
102
|
+
<CapRow type="flex" className="tag-name">
|
|
103
|
+
{personalizationTagColumnText}
|
|
104
|
+
<span className="required-tag-indicator" aria-hidden="true">*</span>
|
|
105
|
+
</CapRow>
|
|
106
|
+
<CapRow type="flex" className="tag-input">
|
|
107
|
+
<CapInput
|
|
108
|
+
type="text"
|
|
109
|
+
isRequired
|
|
110
|
+
className="tag-input-field"
|
|
111
|
+
value={customValues?.[tagKey] ?? ''}
|
|
112
|
+
onChange={(e) => handleCustomValueChange(tagKey, e.target.value)}
|
|
113
|
+
placeholder={formatMessage(messages.enterValue)}
|
|
114
|
+
size="small"
|
|
115
|
+
/>
|
|
116
|
+
</CapRow>
|
|
120
117
|
</CapRow>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
);
|
|
119
|
+
})}
|
|
120
|
+
{(section?.optionalTags || []).map((tag, tagIndex) => {
|
|
121
|
+
const personalizationTagColumnText = getPersonalizationTagColumnLabel(tag);
|
|
122
|
+
const tagKey = tag?.fullPath ?? `optional-${tagIndex}`;
|
|
123
|
+
return (
|
|
124
|
+
<CapRow key={tagKey} type="flex" noWrap className="value-row">
|
|
125
|
+
<CapRow type="flex" className="tag-name">
|
|
126
|
+
{personalizationTagColumnText}
|
|
127
|
+
</CapRow>
|
|
128
|
+
<CapRow type="flex" className="tag-input">
|
|
129
|
+
<CapInput
|
|
130
|
+
type="text"
|
|
131
|
+
className="tag-input-field"
|
|
132
|
+
value={customValues?.[tagKey] ?? ''}
|
|
133
|
+
onChange={(e) => handleCustomValueChange(tagKey, e.target.value)}
|
|
134
|
+
placeholder={formatMessage(messages.enterValue)}
|
|
135
|
+
size="small"
|
|
136
|
+
/>
|
|
137
|
+
</CapRow>
|
|
138
|
+
</CapRow>
|
|
139
|
+
);
|
|
140
|
+
})}
|
|
141
|
+
</CapRow>
|
|
142
|
+
</React.Fragment>
|
|
143
|
+
))}
|
|
125
144
|
</>
|
|
126
145
|
)}
|
|
127
146
|
<CapRow type='flex' align='middle' justify='space-between' className="editor-actions">
|
|
@@ -155,9 +174,12 @@ CustomValuesEditor.propTypes = {
|
|
|
155
174
|
setShowJSON: PropTypes.func.isRequired,
|
|
156
175
|
customValues: PropTypes.object.isRequired,
|
|
157
176
|
handleJSONTextChange: PropTypes.func.isRequired,
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
177
|
+
sections: PropTypes.arrayOf(PropTypes.shape({
|
|
178
|
+
key: PropTypes.string,
|
|
179
|
+
title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
|
180
|
+
requiredTags: PropTypes.array,
|
|
181
|
+
optionalTags: PropTypes.array,
|
|
182
|
+
})).isRequired,
|
|
161
183
|
handleCustomValueChange: PropTypes.func.isRequired,
|
|
162
184
|
handleDiscardCustomValues: PropTypes.func.isRequired,
|
|
163
185
|
handleUpdatePreview: PropTypes.func.isRequired,
|