@capillarytech/creatives-library 8.0.345-alpha.14 → 8.0.345-alpha.15
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/package.json +1 -1
- package/services/tests/api.test.js +13 -0
- package/utils/commonUtils.js +19 -1
- package/utils/rcsPayloadUtils.js +92 -0
- package/utils/templateVarUtils.js +201 -0
- package/utils/tests/templateVarUtils.test.js +204 -0
- package/v2Components/CapActionButton/constants.js +7 -0
- package/v2Components/CapActionButton/index.js +167 -109
- package/v2Components/CapActionButton/index.scss +157 -6
- package/v2Components/CapActionButton/messages.js +19 -3
- package/v2Components/CapActionButton/tests/index.test.js +41 -17
- package/v2Components/CapTagList/index.js +10 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +160 -15
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +18 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +341 -76
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
- package/v2Components/CommonTestAndPreview/constants.js +38 -2
- package/v2Components/CommonTestAndPreview/index.js +676 -186
- package/v2Components/CommonTestAndPreview/messages.js +49 -3
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/v2Components/CommonTestAndPreview/sagas.js +15 -6
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
- 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 +132 -4
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
- package/v2Components/FormBuilder/index.js +8 -10
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
- package/v2Components/SmsFallback/constants.js +73 -0
- package/v2Components/SmsFallback/index.js +955 -0
- package/v2Components/SmsFallback/index.scss +265 -0
- package/v2Components/SmsFallback/messages.js +78 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +118 -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 +197 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +277 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +33 -23
- package/v2Components/TemplatePreview/constants.js +2 -0
- package/v2Components/TemplatePreview/index.js +143 -28
- package/v2Components/TemplatePreview/tests/index.test.js +142 -0
- 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/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
- package/v2Containers/CreativesContainer/constants.js +9 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
- package/v2Containers/CreativesContainer/index.js +300 -103
- package/v2Containers/CreativesContainer/index.scss +51 -1
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +78 -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 +357 -98
- 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/Email/reducer.js +3 -11
- package/v2Containers/Email/sagas.js +5 -9
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +0 -4
- package/v2Containers/Email/tests/sagas.test.js +3 -21
- package/v2Containers/Rcs/constants.js +119 -8
- package/v2Containers/Rcs/index.js +2379 -807
- package/v2Containers/Rcs/index.js.rej +1336 -0
- package/v2Containers/Rcs/index.scss +276 -6
- package/v2Containers/Rcs/index.scss.rej +74 -0
- package/v2Containers/Rcs/messages.js +38 -3
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98018 -70073
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +128 -0
- package/v2Containers/Rcs/tests/index.test.js +152 -121
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
- package/v2Containers/Rcs/tests/utils.test.js +646 -30
- package/v2Containers/Rcs/utils.js +478 -11
- package/v2Containers/Sms/Create/index.js +100 -40
- 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 +636 -130
- package/v2Containers/SmsTrai/Edit/index.scss +121 -0
- package/v2Containers/SmsTrai/Edit/messages.js +14 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
- 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 +163 -2
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +119 -54
- package/v2Containers/Templates/sagas.js +57 -12
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
- package/v2Containers/Templates/tests/sagas.test.js +193 -123
- 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/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useState, useCallback, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {Object} options
|
|
5
|
+
* @param {(params: { page: number, search: string, reset: boolean }) => Promise<{ templates: Array, totalCount: number }>} options.fetchTemplates
|
|
6
|
+
* @param {number} [options.perPage=25]
|
|
7
|
+
*/
|
|
8
|
+
export function useLocalTemplateList({ fetchTemplates, perPage = 25 }) {
|
|
9
|
+
const [listData, setListData] = useState({ templates: [], totalCount: 0 });
|
|
10
|
+
const [loading, setLoading] = useState(false);
|
|
11
|
+
const [page, setPage] = useState(1);
|
|
12
|
+
const [search, setSearchState] = useState('');
|
|
13
|
+
const searchRef = useRef('');
|
|
14
|
+
/** Drops stale responses when a newer fetch starts (search / reset while a request is in flight). */
|
|
15
|
+
const fetchGenerationRef = useRef(0);
|
|
16
|
+
const setSearch = useCallback((value) => {
|
|
17
|
+
const term = typeof value === 'string' ? value : '';
|
|
18
|
+
searchRef.current = term;
|
|
19
|
+
setSearchState(term);
|
|
20
|
+
}, []);
|
|
21
|
+
const lastFetchFullPageRef = useRef(false);
|
|
22
|
+
|
|
23
|
+
const { templates = [], totalCount = 0 } = listData ?? {};
|
|
24
|
+
const hasKnownTotal = (totalCount ?? 0) > 0;
|
|
25
|
+
const hasMoreByTotal = (totalCount ?? 0) > (templates?.length ?? 0);
|
|
26
|
+
const hasMoreByFullPage =
|
|
27
|
+
!hasKnownTotal && lastFetchFullPageRef.current && (templates?.length ?? 0) > 0;
|
|
28
|
+
const canLoadMore = (hasMoreByTotal || hasMoreByFullPage) && !loading;
|
|
29
|
+
|
|
30
|
+
const runFetch = useCallback(
|
|
31
|
+
async ({ page: p = 1, reset = true, search: searchTerm } = {}) => {
|
|
32
|
+
const term = searchTerm !== undefined ? searchTerm : searchRef.current;
|
|
33
|
+
const gen = ++fetchGenerationRef.current;
|
|
34
|
+
setLoading(true);
|
|
35
|
+
try {
|
|
36
|
+
const result = await fetchTemplates({ page: p, search: term, reset });
|
|
37
|
+
if (gen !== fetchGenerationRef.current) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const nextTemplates = result?.templates ?? [];
|
|
41
|
+
const nextTotalCount = result?.totalCount ?? 0;
|
|
42
|
+
lastFetchFullPageRef.current = nextTemplates.length >= perPage;
|
|
43
|
+
setListData((prev) => ({
|
|
44
|
+
templates: reset ? nextTemplates : [...(prev.templates || []), ...nextTemplates],
|
|
45
|
+
totalCount: nextTotalCount > 0 ? nextTotalCount : (reset ? 0 : prev.totalCount),
|
|
46
|
+
}));
|
|
47
|
+
setPage(p);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
if (gen !== fetchGenerationRef.current) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
lastFetchFullPageRef.current = false;
|
|
53
|
+
if (reset) {
|
|
54
|
+
setListData({ templates: [], totalCount: 0 });
|
|
55
|
+
setPage(1);
|
|
56
|
+
}
|
|
57
|
+
} finally {
|
|
58
|
+
if (gen === fetchGenerationRef.current) {
|
|
59
|
+
setLoading(false);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
[fetchTemplates, perPage]
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const loadMore = useCallback(() => {
|
|
67
|
+
if (!canLoadMore) return;
|
|
68
|
+
runFetch({ page: page + 1, reset: false, search: searchRef.current });
|
|
69
|
+
}, [canLoadMore, page, runFetch]);
|
|
70
|
+
|
|
71
|
+
const reset = useCallback(
|
|
72
|
+
(searchTerm) => {
|
|
73
|
+
const term = searchTerm !== undefined ? searchTerm : searchRef.current;
|
|
74
|
+
setSearch(term);
|
|
75
|
+
lastFetchFullPageRef.current = false;
|
|
76
|
+
runFetch({ page: 1, reset: true, search: term });
|
|
77
|
+
},
|
|
78
|
+
[runFetch, setSearch]
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
templates,
|
|
83
|
+
totalCount,
|
|
84
|
+
loading,
|
|
85
|
+
page,
|
|
86
|
+
search,
|
|
87
|
+
setSearch,
|
|
88
|
+
loadMore,
|
|
89
|
+
reset,
|
|
90
|
+
canLoadMore,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -537,20 +537,21 @@
|
|
|
537
537
|
.unicode-disabled{
|
|
538
538
|
font-size: 16px;
|
|
539
539
|
}
|
|
540
|
+
position: absolute;
|
|
541
|
+
overflow: auto;
|
|
542
|
+
top: 0;
|
|
543
|
+
left: 17%;
|
|
544
|
+
white-space: pre-wrap;
|
|
545
|
+
word-break: break-word;
|
|
546
|
+
max-height: 100%;
|
|
547
|
+
line-height: 14px;
|
|
548
|
+
font-size: 10px;
|
|
549
|
+
font-family: 'open-sans';
|
|
550
|
+
|
|
540
551
|
&.sms {
|
|
541
|
-
max-height: 100%;
|
|
542
|
-
position: absolute;
|
|
543
552
|
// width: initial;
|
|
544
|
-
overflow: auto;
|
|
545
|
-
top: 0;
|
|
546
|
-
left: 17%;
|
|
547
|
-
white-space: pre-wrap;
|
|
548
|
-
word-break: break-word;
|
|
549
|
-
line-height: 14px;
|
|
550
553
|
padding: 0 8px 8px 8px;
|
|
551
|
-
font-size: 10px;
|
|
552
554
|
width: 100%;
|
|
553
|
-
font-family: 'open-sans';
|
|
554
555
|
.rcs-image{
|
|
555
556
|
width: 100%;
|
|
556
557
|
height: 123px;
|
|
@@ -582,22 +583,12 @@
|
|
|
582
583
|
&:not(.sms){
|
|
583
584
|
padding: 4px;
|
|
584
585
|
background: #3F51B5;
|
|
585
|
-
position: absolute;
|
|
586
586
|
// width: initial;
|
|
587
|
-
overflow: auto;
|
|
588
|
-
top: 0;
|
|
589
|
-
left: 17%;
|
|
590
|
-
white-space: pre-wrap;
|
|
591
|
-
word-break: break-word;
|
|
592
587
|
border-radius: 4px;
|
|
593
|
-
max-height: 100%;
|
|
594
588
|
color: #FFFFFF;
|
|
595
589
|
width: 70%;
|
|
596
590
|
min-height: 26px;
|
|
597
|
-
line-height: 14px;
|
|
598
591
|
padding: 8px;
|
|
599
|
-
font-size: 10px;
|
|
600
|
-
font-family: 'open-sans';
|
|
601
592
|
}
|
|
602
593
|
&.message-pop-carousel {
|
|
603
594
|
position: relative;
|
|
@@ -842,6 +833,27 @@
|
|
|
842
833
|
}
|
|
843
834
|
}
|
|
844
835
|
|
|
836
|
+
.shell-v2.rcs-preview {
|
|
837
|
+
// Override `.message-pop:not(.sms)` (blue background) for RCS carousel cards.
|
|
838
|
+
.msg-container.sms {
|
|
839
|
+
.message-pop.sms {
|
|
840
|
+
.rcs-carousel-card {
|
|
841
|
+
background: $CAP_WHITE;
|
|
842
|
+
color: $CAP_G01;
|
|
843
|
+
width: 10.4rem;
|
|
844
|
+
left: 0;
|
|
845
|
+
flex-shrink: 0;
|
|
846
|
+
padding: $CAP_SPACE_04 0 $CAP_SPACE_08;
|
|
847
|
+
border-radius: 0.428rem;
|
|
848
|
+
|
|
849
|
+
.carousel-title {
|
|
850
|
+
font-weight: 700 !important;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
845
857
|
.align-center {
|
|
846
858
|
text-align: center;
|
|
847
859
|
}
|
|
@@ -1012,9 +1024,7 @@
|
|
|
1012
1024
|
top: 0;
|
|
1013
1025
|
}
|
|
1014
1026
|
.video-icon {
|
|
1015
|
-
position:
|
|
1016
|
-
right: -17px;
|
|
1017
|
-
bottom: -17px;
|
|
1027
|
+
position: sticky;
|
|
1018
1028
|
}
|
|
1019
1029
|
|
|
1020
1030
|
.zalo-preview-container {
|
|
@@ -52,6 +52,7 @@ import { QUICK_REPLY, WHATSAPP_CATEGORIES, PHONE_NUMBER } from '../../v2Containe
|
|
|
52
52
|
import { RCS_BUTTON_TYPES, LEFT, HORIZONTAL, VERTICAL, RIGHT} from '../../v2Containers/Rcs/constants';
|
|
53
53
|
import { ANDROID, INAPP_MESSAGE_LAYOUT_TYPES } from '../../v2Containers/InApp/constants';
|
|
54
54
|
import { CAROUSEL } from '../../v2Containers/MobilePushNew/constants';
|
|
55
|
+
import { TEMPLATE_VAR_REGEX } from './constants';
|
|
55
56
|
|
|
56
57
|
const wechatBodyNew = require('./assets/images/wechat_mobile_android.svg');
|
|
57
58
|
const smsMobileAndroid = require('./assets/images/sms_mobile_android.svg');
|
|
@@ -234,7 +235,23 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
234
235
|
let content = channel && channel.toLowerCase() === 'sms' ? [this.props.content] : this.props.content;
|
|
235
236
|
const { formatMessage } = intl;
|
|
236
237
|
const { rcsPreviewContent, inAppPreviewContent, viberPreviewContent, isBeeFreeTemplate } = content || {};
|
|
237
|
-
const
|
|
238
|
+
const normalizedRcsPreviewContent = Array.isArray(rcsPreviewContent)
|
|
239
|
+
? { carouselData: rcsPreviewContent }
|
|
240
|
+
: (rcsPreviewContent || {});
|
|
241
|
+
const {
|
|
242
|
+
rcsImageSrc,
|
|
243
|
+
rcsVideoSrc,
|
|
244
|
+
rcsTitle,
|
|
245
|
+
rcsDesc,
|
|
246
|
+
rcsSuggestions,
|
|
247
|
+
carouselData: rcsCarouselData,
|
|
248
|
+
rcsCarouselData: rcsCarouselDataAlt,
|
|
249
|
+
cardVarMapped: rcsCardVarMapped,
|
|
250
|
+
} = normalizedRcsPreviewContent;
|
|
251
|
+
const resolvedRcsCarouselData =
|
|
252
|
+
Array.isArray(rcsCarouselData) && rcsCarouselData.length > 0
|
|
253
|
+
? rcsCarouselData
|
|
254
|
+
: rcsCarouselDataAlt;
|
|
238
255
|
const {
|
|
239
256
|
videoParams,
|
|
240
257
|
imageURL,
|
|
@@ -315,6 +332,18 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
315
332
|
'flex-shrink': 0,
|
|
316
333
|
'left': 0,
|
|
317
334
|
};
|
|
335
|
+
|
|
336
|
+
const resolveVarsWithMap = (input, varMap) => {
|
|
337
|
+
if (input === null || input === undefined) return '';
|
|
338
|
+
const str = typeof input === 'string' ? input : String(input);
|
|
339
|
+
return str.replace(TEMPLATE_VAR_REGEX, (token, varName) => {
|
|
340
|
+
const mappedValue = varMap?.[varName];
|
|
341
|
+
if (mappedValue === null || mappedValue === undefined || String(mappedValue) === '') {
|
|
342
|
+
return token;
|
|
343
|
+
}
|
|
344
|
+
return String(mappedValue);
|
|
345
|
+
});
|
|
346
|
+
};
|
|
318
347
|
const getVideoContent = ({
|
|
319
348
|
video,
|
|
320
349
|
actionUrl,
|
|
@@ -471,13 +500,15 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
471
500
|
const whatsappUpdatedAccountName = whatsappAccountName || templateData?.versions?.base?.content?.whatsapp?.accountName || '';
|
|
472
501
|
const whatsappUpdatedLen = whatsappContentLen !== undefined ? whatsappContentLen : content?.charCount;
|
|
473
502
|
|
|
474
|
-
const renderRcsSuggestionsPreview = () => {
|
|
503
|
+
const renderRcsSuggestionsPreview = (suggestionsArg) => {
|
|
475
504
|
const renderArray = [];
|
|
476
|
-
(rcsSuggestions || [])
|
|
477
|
-
|
|
505
|
+
const suggestions = Array.isArray(suggestionsArg) ? suggestionsArg : (rcsSuggestions || []);
|
|
506
|
+
(suggestions || []).forEach((suggestion, idx) => {
|
|
507
|
+
const suggestionKey = `${suggestion?.type || 'unknown'}-${suggestion?.text || ''}-${idx}`;
|
|
508
|
+
renderArray.push(<CapDivider key={`rcs-divider-${suggestionKey}`} className="whatsapp-divider" />);
|
|
478
509
|
if (suggestion.type === RCS_BUTTON_TYPES.QUICK_REPLY) {
|
|
479
510
|
renderArray.push(
|
|
480
|
-
<CapLabel type="label21" className="rcs-cta-preview">
|
|
511
|
+
<CapLabel key={`rcs-suggestion-${suggestionKey}`} type="label21" className="rcs-cta-preview">
|
|
481
512
|
<CapIcon
|
|
482
513
|
type='small-link'
|
|
483
514
|
size="xs"
|
|
@@ -487,14 +518,14 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
487
518
|
);
|
|
488
519
|
} else if (suggestion.type === RCS_BUTTON_TYPES.CTA) {
|
|
489
520
|
renderArray.push(
|
|
490
|
-
<CapLabel type="label21" className="rcs-cta-preview">
|
|
521
|
+
<CapLabel key={`rcs-suggestion-${suggestionKey}`} type="label21" className="rcs-cta-preview">
|
|
491
522
|
<CapIcon type="launch" size="xs" />
|
|
492
523
|
{suggestion.text}
|
|
493
524
|
</CapLabel>,
|
|
494
525
|
);
|
|
495
526
|
} else if (suggestion.type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
|
|
496
527
|
renderArray.push(
|
|
497
|
-
<CapLabel type="label21" className="rcs-cta-preview">
|
|
528
|
+
<CapLabel key={`rcs-suggestion-${suggestionKey}`} type="label21" className="rcs-cta-preview">
|
|
498
529
|
<CapIcon type="call" size="xs" />
|
|
499
530
|
{suggestion.text}
|
|
500
531
|
</CapLabel>,
|
|
@@ -513,7 +544,7 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
513
544
|
className="message-pop-item align-left rcs-content"
|
|
514
545
|
fontWeight="bold"
|
|
515
546
|
>
|
|
516
|
-
{rcsTitle}
|
|
547
|
+
{resolveVarsWithMap(rcsTitle, rcsCardVarMapped)}
|
|
517
548
|
</CapLabel>
|
|
518
549
|
<CapDivider className="whatsapp-divider" />
|
|
519
550
|
</>
|
|
@@ -523,7 +554,7 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
523
554
|
type="label5"
|
|
524
555
|
className="message-pop-item align-left rcs-desc rcs-content"
|
|
525
556
|
>
|
|
526
|
-
{rcsDesc}
|
|
557
|
+
{resolveVarsWithMap(rcsDesc, rcsCardVarMapped)}
|
|
527
558
|
</CapLabel>
|
|
528
559
|
)}
|
|
529
560
|
{renderRcsSuggestionsPreview()}
|
|
@@ -540,50 +571,134 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
540
571
|
/>
|
|
541
572
|
)}
|
|
542
573
|
{rcsVideoSrc && (
|
|
543
|
-
<
|
|
574
|
+
<CapRow className="video-preview">
|
|
544
575
|
<CapImage
|
|
545
576
|
src={rcsVideoSrc}
|
|
546
577
|
className="rcs-image"
|
|
547
578
|
alt={formatMessage(messages.previewGenerated)}
|
|
548
579
|
/>
|
|
549
|
-
<
|
|
580
|
+
<CapRow className="icon-position">
|
|
550
581
|
<CapImage
|
|
551
582
|
className="video-icon"
|
|
552
583
|
src={videoPlay}
|
|
553
584
|
/>
|
|
554
|
-
</
|
|
555
|
-
</
|
|
585
|
+
</CapRow>
|
|
586
|
+
</CapRow>
|
|
556
587
|
)}
|
|
557
588
|
</>
|
|
558
589
|
);
|
|
559
590
|
|
|
560
591
|
const renderRcsPreviewContent = () => {
|
|
592
|
+
const carouselCards = Array.isArray(resolvedRcsCarouselData) ? resolvedRcsCarouselData : [];
|
|
593
|
+
if (carouselCards.length > 0) {
|
|
594
|
+
return (
|
|
595
|
+
<CapRow className="msg-container sms">
|
|
596
|
+
<CapRow className="message-pop sms">
|
|
597
|
+
<CapRow className="msg-container-carousel">
|
|
598
|
+
<CapRow className="scroll-container">
|
|
599
|
+
{carouselCards.map((card, idx) => {
|
|
600
|
+
const key = `rcs-carousel-${idx}-${card?.bodyText || card?.imageSrc || card?.videoPreviewImg || ''}`;
|
|
601
|
+
const isVideo = (card?.mediaType || '').toLowerCase() === 'video';
|
|
602
|
+
const cardSuggestions = Array.isArray(card?.suggestions) ? card.suggestions : [];
|
|
603
|
+
const effectiveCardVarMap = card?.cardVarMapped || rcsCardVarMapped;
|
|
604
|
+
const suggestionsNode = cardSuggestions.length > 0
|
|
605
|
+
? renderRcsSuggestionsPreview(cardSuggestions)
|
|
606
|
+
: null;
|
|
607
|
+
|
|
608
|
+
const resolvedCardTitle = resolveVarsWithMap(card?.title, effectiveCardVarMap);
|
|
609
|
+
const resolvedCardBodyText = resolveVarsWithMap(card?.bodyText, effectiveCardVarMap);
|
|
610
|
+
|
|
611
|
+
return (
|
|
612
|
+
<CapRow
|
|
613
|
+
key={key}
|
|
614
|
+
className="message-pop align-left message-pop-carousel rcs-carousel-card"
|
|
615
|
+
>
|
|
616
|
+
<CapRow className="whatsapp-content">
|
|
617
|
+
{!isVideo && (
|
|
618
|
+
<CapImage
|
|
619
|
+
src={card?.imageSrc ? card.imageSrc : whatsappImageEmptyPreview}
|
|
620
|
+
className="whatsapp-image"
|
|
621
|
+
alt={formatMessage(messages.previewGenerated)}
|
|
622
|
+
/>
|
|
623
|
+
)}
|
|
624
|
+
{isVideo && (
|
|
625
|
+
<CapTooltip title={formatMessage(messages.videoPreviewTooltip)}>
|
|
626
|
+
<CapRow className="video-preview">
|
|
627
|
+
<CapImage
|
|
628
|
+
src={card?.videoPreviewImg ? card.videoPreviewImg : whatsappVideoEmptyPreview}
|
|
629
|
+
className="whatsapp-image"
|
|
630
|
+
alt={formatMessage(messages.previewGenerated)}
|
|
631
|
+
/>
|
|
632
|
+
<CapRow className="icon-position">
|
|
633
|
+
<CapImage className="video-icon" src={videoPlay} alt="Play" />
|
|
634
|
+
</CapRow>
|
|
635
|
+
</CapRow>
|
|
636
|
+
</CapTooltip>
|
|
637
|
+
)}
|
|
638
|
+
|
|
639
|
+
{(resolvedCardTitle || resolvedCardBodyText) && (
|
|
640
|
+
<CapRow className="carousel-content">
|
|
641
|
+
{!!resolvedCardTitle && (
|
|
642
|
+
<CapLabel
|
|
643
|
+
type={card?.titleLabelType || 'label1'}
|
|
644
|
+
className="carousel-title"
|
|
645
|
+
>
|
|
646
|
+
{resolvedCardTitle}
|
|
647
|
+
</CapLabel>
|
|
648
|
+
)}
|
|
649
|
+
{!!resolvedCardBodyText && (
|
|
650
|
+
<CapLabel
|
|
651
|
+
type={card?.bodyLabelType || 'label2'}
|
|
652
|
+
className="carousel-message"
|
|
653
|
+
>
|
|
654
|
+
{resolvedCardBodyText}
|
|
655
|
+
</CapLabel>
|
|
656
|
+
)}
|
|
657
|
+
</CapRow>
|
|
658
|
+
)}
|
|
659
|
+
|
|
660
|
+
{!!suggestionsNode && (
|
|
661
|
+
<>
|
|
662
|
+
{suggestionsNode}
|
|
663
|
+
</>
|
|
664
|
+
)}
|
|
665
|
+
</CapRow>
|
|
666
|
+
</CapRow>
|
|
667
|
+
);
|
|
668
|
+
})}
|
|
669
|
+
</CapRow>
|
|
670
|
+
</CapRow>
|
|
671
|
+
</CapRow>
|
|
672
|
+
</CapRow>
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
561
676
|
if (rcsOrientation === HORIZONTAL) {
|
|
562
677
|
return rcsType === RIGHT ? (
|
|
563
|
-
<
|
|
564
|
-
<
|
|
678
|
+
<CapRow className="msg-container sms">
|
|
679
|
+
<CapRow className="message-pop sms horizontal">
|
|
565
680
|
<CapColumn className="rcs-preview-text" span={12}>{renderTextPreviewContent()}</CapColumn>
|
|
566
681
|
<CapColumn span={12}>{renderMediaPreviewContent()}</CapColumn>
|
|
567
|
-
</
|
|
568
|
-
</
|
|
682
|
+
</CapRow>
|
|
683
|
+
</CapRow>
|
|
569
684
|
|
|
570
685
|
) : (
|
|
571
|
-
<
|
|
572
|
-
<
|
|
686
|
+
<CapRow className="msg-container sms">
|
|
687
|
+
<CapRow className="message-pop sms horizontal">
|
|
573
688
|
<CapColumn span={12}>{renderMediaPreviewContent()}</CapColumn>
|
|
574
689
|
<CapColumn className="rcs-preview-text" span={12}>{renderTextPreviewContent()}</CapColumn>
|
|
575
|
-
</
|
|
576
|
-
</
|
|
690
|
+
</CapRow>
|
|
691
|
+
</CapRow>
|
|
577
692
|
);
|
|
578
693
|
}
|
|
579
694
|
|
|
580
695
|
return (
|
|
581
|
-
<
|
|
582
|
-
<
|
|
696
|
+
<CapRow className="msg-container sms">
|
|
697
|
+
<CapRow className="message-pop sms">
|
|
583
698
|
{renderMediaPreviewContent()}
|
|
584
699
|
{renderTextPreviewContent()}
|
|
585
|
-
</
|
|
586
|
-
</
|
|
700
|
+
</CapRow>
|
|
701
|
+
</CapRow>
|
|
587
702
|
);
|
|
588
703
|
};
|
|
589
704
|
|
|
@@ -1291,14 +1406,14 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
1291
1406
|
""
|
|
1292
1407
|
)}
|
|
1293
1408
|
{channel?.toUpperCase() === RCS && (
|
|
1294
|
-
<
|
|
1409
|
+
<CapRow className="shell-v2 align-center rcs-preview">
|
|
1295
1410
|
<CapImage
|
|
1296
1411
|
className="preview-image"
|
|
1297
1412
|
src={rcsIosPreview ? smsMobileIos : smsMobileAndroid}
|
|
1298
1413
|
alt={formatMessage(messages.previewGenerated)}
|
|
1299
1414
|
/>
|
|
1300
1415
|
{renderRcsPreviewContent()}
|
|
1301
|
-
</
|
|
1416
|
+
</CapRow>
|
|
1302
1417
|
|
|
1303
1418
|
)}
|
|
1304
1419
|
{channel?.toUpperCase() === ZALO && (
|
|
@@ -1499,4 +1614,4 @@ TemplatePreview.propTypes = {
|
|
|
1499
1614
|
rcsOrientation: PropTypes.string,
|
|
1500
1615
|
};
|
|
1501
1616
|
|
|
1502
|
-
export default (injectIntl(TemplatePreview));
|
|
1617
|
+
export default (injectIntl(TemplatePreview));
|
|
@@ -2,6 +2,9 @@ import React from 'react';
|
|
|
2
2
|
import { shallowWithIntl } from '../../../helpers/intl-enzym-test-helpers';
|
|
3
3
|
|
|
4
4
|
import { TemplatePreview } from '../index';
|
|
5
|
+
import { RCS } from '../../../v2Containers/CreativesContainer/constants';
|
|
6
|
+
import whatsappImageEmptyPreview from '../assets/images/empty_image_preview.svg';
|
|
7
|
+
import whatsappVideoEmptyPreview from '../assets/images/empty_video_preview.svg';
|
|
5
8
|
|
|
6
9
|
describe('Test Templates container', () => {
|
|
7
10
|
let renderedComponent;
|
|
@@ -69,4 +72,143 @@ describe('Test Templates container', () => {
|
|
|
69
72
|
});
|
|
70
73
|
expect(renderedComponent).toMatchSnapshot();
|
|
71
74
|
});
|
|
75
|
+
|
|
76
|
+
describe('RCS carousel preview branches', () => {
|
|
77
|
+
const buildRcsContent = (cards) => ({
|
|
78
|
+
rcsPreviewContent: {
|
|
79
|
+
carouselData: cards,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const getNodesByClassName = (className) =>
|
|
84
|
+
renderedComponent.findWhere((node) => node.prop('className') === className);
|
|
85
|
+
|
|
86
|
+
it('renders image and video cards with proper media-specific markup', () => {
|
|
87
|
+
renderFunction(
|
|
88
|
+
RCS,
|
|
89
|
+
buildRcsContent([
|
|
90
|
+
{
|
|
91
|
+
mediaType: 'image',
|
|
92
|
+
imageSrc: 'https://example.com/image-card.jpg',
|
|
93
|
+
bodyText: 'image body',
|
|
94
|
+
suggestions: [],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
mediaType: 'video',
|
|
98
|
+
videoPreviewImg: 'https://example.com/video-thumb.jpg',
|
|
99
|
+
bodyText: 'video body',
|
|
100
|
+
suggestions: [],
|
|
101
|
+
},
|
|
102
|
+
]),
|
|
103
|
+
'',
|
|
104
|
+
0,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Video card should render tooltip/video icon path; image-only card should not.
|
|
108
|
+
expect(renderedComponent.find('CapTooltip')).toHaveLength(1);
|
|
109
|
+
expect(
|
|
110
|
+
renderedComponent.findWhere((node) => node.prop('className') === 'video-icon')
|
|
111
|
+
).toHaveLength(1);
|
|
112
|
+
|
|
113
|
+
// Both cards render media containers.
|
|
114
|
+
expect(
|
|
115
|
+
renderedComponent.findWhere((node) => node.prop('className') === 'whatsapp-image')
|
|
116
|
+
).toHaveLength(2);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('renders per-card suggestions only for cards that provide suggestions', () => {
|
|
120
|
+
renderFunction(
|
|
121
|
+
RCS,
|
|
122
|
+
buildRcsContent([
|
|
123
|
+
{
|
|
124
|
+
mediaType: 'image',
|
|
125
|
+
imageSrc: 'https://example.com/with-suggestions.jpg',
|
|
126
|
+
bodyText: 'has suggestions',
|
|
127
|
+
suggestions: [
|
|
128
|
+
{ type: 'QUICK_REPLY', text: 'Reply 1' },
|
|
129
|
+
{ type: 'CTA', text: 'Visit' },
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
mediaType: 'image',
|
|
134
|
+
imageSrc: 'https://example.com/no-suggestions.jpg',
|
|
135
|
+
bodyText: 'no suggestions',
|
|
136
|
+
suggestions: [],
|
|
137
|
+
},
|
|
138
|
+
]),
|
|
139
|
+
'',
|
|
140
|
+
0,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Suggestion labels/icons should be rendered only from first card.
|
|
144
|
+
expect(getNodesByClassName('rcs-cta-preview')).toHaveLength(2);
|
|
145
|
+
expect(getNodesByClassName('whatsapp-divider')).not.toHaveLength(0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('falls back to empty preview assets when image/video media src is missing', () => {
|
|
149
|
+
renderFunction(
|
|
150
|
+
RCS,
|
|
151
|
+
buildRcsContent([
|
|
152
|
+
{
|
|
153
|
+
mediaType: 'image',
|
|
154
|
+
bodyText: 'fallback image',
|
|
155
|
+
suggestions: [],
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
mediaType: 'video',
|
|
159
|
+
bodyText: 'fallback video',
|
|
160
|
+
suggestions: [],
|
|
161
|
+
},
|
|
162
|
+
]),
|
|
163
|
+
'',
|
|
164
|
+
0,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const mediaImageNodes = renderedComponent.findWhere(
|
|
168
|
+
(node) => node.name() === 'CapImage' && node.prop('className') === 'whatsapp-image'
|
|
169
|
+
);
|
|
170
|
+
const mediaSrcs = mediaImageNodes.map((node) => node.prop('src'));
|
|
171
|
+
|
|
172
|
+
expect(mediaSrcs).toContain(whatsappImageEmptyPreview);
|
|
173
|
+
expect(mediaSrcs).toContain(whatsappVideoEmptyPreview);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('generates unique carousel keys using rcs-carousel-<idx>-<bodyText|imageSrc|videoPreviewImg>', () => {
|
|
177
|
+
renderFunction(
|
|
178
|
+
RCS,
|
|
179
|
+
buildRcsContent([
|
|
180
|
+
{
|
|
181
|
+
mediaType: 'image',
|
|
182
|
+
imageSrc: 'https://example.com/one.jpg',
|
|
183
|
+
bodyText: 'same body',
|
|
184
|
+
suggestions: [],
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
mediaType: 'image',
|
|
188
|
+
imageSrc: 'https://example.com/two.jpg',
|
|
189
|
+
bodyText: 'same body',
|
|
190
|
+
suggestions: [],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
mediaType: 'video',
|
|
194
|
+
videoPreviewImg: 'https://example.com/three-thumb.jpg',
|
|
195
|
+
bodyText: 'same body',
|
|
196
|
+
suggestions: [],
|
|
197
|
+
},
|
|
198
|
+
]),
|
|
199
|
+
'',
|
|
200
|
+
0,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const cardNodes = getNodesByClassName('message-pop align-left message-pop-carousel rcs-carousel-card');
|
|
204
|
+
const keys = cardNodes.map((node) => node.key());
|
|
205
|
+
|
|
206
|
+
expect(keys).toEqual([
|
|
207
|
+
'rcs-carousel-0-same body',
|
|
208
|
+
'rcs-carousel-1-same body',
|
|
209
|
+
'rcs-carousel-2-same body',
|
|
210
|
+
]);
|
|
211
|
+
expect(new Set(keys).size).toBe(keys.length);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
72
214
|
});
|
|
@@ -18,7 +18,7 @@ import injectReducer from '../../utils/injectReducer';
|
|
|
18
18
|
import injectSaga from '../../utils/injectSaga';
|
|
19
19
|
|
|
20
20
|
import CommonTestAndPreview from '../CommonTestAndPreview';
|
|
21
|
-
import { CHANNELS } from '../CommonTestAndPreview/constants';
|
|
21
|
+
import { CHANNELS, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../CommonTestAndPreview/constants';
|
|
22
22
|
import * as commonTestAndPreviewActions from '../CommonTestAndPreview/actions';
|
|
23
23
|
import { commonTestAndPreviewSaga } from '../CommonTestAndPreview/sagas';
|
|
24
24
|
import commonTestAndPreviewReducer from '../CommonTestAndPreview/reducer';
|
|
@@ -78,6 +78,16 @@ TestAndPreviewSlidebox.propTypes = {
|
|
|
78
78
|
content: PropTypes.string,
|
|
79
79
|
beeInstance: PropTypes.object,
|
|
80
80
|
currentTab: PropTypes.number,
|
|
81
|
+
smsFallbackContent: PropTypes.shape({
|
|
82
|
+
templateContent: PropTypes.string,
|
|
83
|
+
senderId: PropTypes.string,
|
|
84
|
+
templateName: PropTypes.string,
|
|
85
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: PropTypes.object,
|
|
86
|
+
}),
|
|
87
|
+
/** Passed to CommonTestAndPreview for RCS test-meta resolution (slot semantics vs full-mode). */
|
|
88
|
+
rcsTestPreviewOptions: PropTypes.shape({
|
|
89
|
+
isLibraryMode: PropTypes.bool,
|
|
90
|
+
}),
|
|
81
91
|
// Redux props are passed through
|
|
82
92
|
actions: PropTypes.object.isRequired,
|
|
83
93
|
extractedTags: PropTypes.array.isRequired,
|
|
@@ -109,10 +119,12 @@ TestAndPreviewSlidebox.defaultProps = {
|
|
|
109
119
|
currentTab: 1,
|
|
110
120
|
messageMetaConfigId: null,
|
|
111
121
|
prefilledValues: {},
|
|
122
|
+
rcsTestPreviewOptions: undefined,
|
|
112
123
|
senderDetailsByChannel: {},
|
|
113
124
|
wecrmAccounts: [],
|
|
114
125
|
isLoadingSenderDetails: false,
|
|
115
126
|
orgUnitId: -1,
|
|
127
|
+
smsFallbackContent: null,
|
|
116
128
|
};
|
|
117
129
|
|
|
118
130
|
const mapStateToProps = createStructuredSelector({
|