@capillarytech/creatives-library 9.0.36-alpha.0 → 9.0.37-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/translations/en.json +2 -2
- package/utils/permissions.js +59 -0
- package/utils/tests/permissions.test.js +116 -0
- package/v2Components/AccessForbidden/_accessForbidden.scss +14 -0
- package/v2Components/AccessForbidden/index.js +5 -3
- package/v2Components/AccessForbidden/messages.js +3 -2
- package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +15 -108
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +1 -139
- package/v2Components/CommonTestAndPreview/index.js +26 -240
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +0 -364
- package/v2Components/NoPermissionWrapper/_noPermissionWrapper.scss +12 -0
- package/v2Components/NoPermissionWrapper/index.js +26 -0
- package/v2Containers/App/constants.js +5 -0
- package/v2Containers/Assets/Gallery/_gallery.scss +6 -0
- package/v2Containers/Assets/Gallery/index.js +34 -10
- package/v2Containers/Cap/index.js +5 -2
- package/v2Containers/Cap/messages.js +4 -0
- package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +2 -2
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +6 -6
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +4 -4
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +32 -32
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +36 -36
- package/v2Containers/Rcs/carouselUtils.js +73 -46
- package/v2Containers/Rcs/components/CarouselCard.js +9 -6
- package/v2Containers/Rcs/constants.js +7 -2
- package/v2Containers/Rcs/index.js +126 -72
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +43 -5
- package/v2Containers/Rcs/tests/CarouselCard.test.js +2 -1
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +118 -118
- package/v2Containers/Rcs/tests/carouselUtils.test.js +15 -15
- package/v2Containers/Rcs/tests/index.test.js +232 -10
- package/v2Containers/Rcs/tests/utils.test.js +17 -0
- package/v2Containers/Rcs/utils.js +51 -12
- package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +8 -8
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +18 -18
- package/v2Containers/Templates/ChannelTypeIllustration.js +10 -1
- package/v2Containers/Templates/_templates.scss +6 -114
- package/v2Containers/Templates/index.js +151 -190
- package/v2Containers/Templates/tests/ChannelTypeIllustration.test.js +7 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +173 -120
- package/v2Containers/TemplatesV2/index.js +16 -5
- package/v2Containers/Viber/constants.js +0 -21
- package/v2Containers/Viber/index.js +49 -719
- package/v2Containers/Viber/index.scss +0 -175
- package/v2Containers/Viber/messages.js +0 -121
- package/v2Containers/Viber/tests/index.test.js +0 -80
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +268 -268
- package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberCarouselPreviewCards.js +0 -132
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_viberCarouselPreviewCards.scss +0 -131
|
@@ -73,7 +73,6 @@ import * as ebillActions from '../Ebill/actions';
|
|
|
73
73
|
import * as emailActions from '../Email/actions';
|
|
74
74
|
import * as lineActions from '../Line/Container/actions';
|
|
75
75
|
import * as viberActions from '../Viber/actions';
|
|
76
|
-
import { VIBER_MEDIA_TYPES } from '../Viber/constants';
|
|
77
76
|
import * as facebookActions from '../Facebook/actions';
|
|
78
77
|
import * as whatsappActions from '../Whatsapp/actions';
|
|
79
78
|
import * as rcsActions from '../Rcs/actions';
|
|
@@ -98,6 +97,9 @@ import WhatsappStatusContainer from '../../v2Components/WhatsappStatusContainer'
|
|
|
98
97
|
import WechatRichmediaTemplatePreview from '../../v2Components/TemplatePreview/WechatRichmediaTemplatePreview';
|
|
99
98
|
import CapCustomSkeleton from '../../v2Components/CapCustomSkeleton';
|
|
100
99
|
import ChannelTypeIllustration from './ChannelTypeIllustration';
|
|
100
|
+
import NoPermissionWrapper from '../../v2Components/NoPermissionWrapper';
|
|
101
|
+
import { getUserAccess, hasPermission, PERMISSIONS } from '../../utils/permissions';
|
|
102
|
+
|
|
101
103
|
import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
|
|
102
104
|
import creativesMessages from '../CreativesContainer/messages';
|
|
103
105
|
import {
|
|
@@ -1397,11 +1399,6 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
1397
1399
|
const image = viberContent.image || {};
|
|
1398
1400
|
const video = viberContent.video || {};
|
|
1399
1401
|
const button = viberContent.button || {};
|
|
1400
|
-
const messageType = viberContent.type || '';
|
|
1401
|
-
const cardsRaw = viberContent.cards;
|
|
1402
|
-
const cards = Array.isArray(cardsRaw) ? cardsRaw : [];
|
|
1403
|
-
const isCarousel =
|
|
1404
|
-
messageType === VIBER_MEDIA_TYPES.CAROUSEL || cards.length > 0;
|
|
1405
1402
|
|
|
1406
1403
|
const viberPreview = {
|
|
1407
1404
|
messageContent: text,
|
|
@@ -1419,39 +1416,14 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
1419
1416
|
};
|
|
1420
1417
|
}
|
|
1421
1418
|
|
|
1422
|
-
if (isCarousel) {
|
|
1423
|
-
viberPreview.type = VIBER_MEDIA_TYPES.CAROUSEL;
|
|
1424
|
-
viberPreview.cards = cards.map((card) => ({
|
|
1425
|
-
text: card?.text || '',
|
|
1426
|
-
mediaUrl: card?.mediaUrl || '',
|
|
1427
|
-
buttons: (card?.buttons || []).map((btn) => ({
|
|
1428
|
-
title: btn?.title || '',
|
|
1429
|
-
action: btn?.action || '',
|
|
1430
|
-
})),
|
|
1431
|
-
}));
|
|
1432
|
-
viberPreview.showCarouselEditorPreview = true;
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
1419
|
// Extract account name
|
|
1436
1420
|
const accountName = get(template, 'definition.accountName', '');
|
|
1437
1421
|
|
|
1438
1422
|
// Return Viber content object (same as Viber/index.js getTemplateContent)
|
|
1439
1423
|
return {
|
|
1440
1424
|
viberPreviewContent: viberPreview,
|
|
1441
|
-
accountName: accountName
|
|
1442
|
-
brandName: accountName
|
|
1443
|
-
messageContent: text,
|
|
1444
|
-
...(isCarousel && {
|
|
1445
|
-
type: VIBER_MEDIA_TYPES.CAROUSEL,
|
|
1446
|
-
cards: viberPreview.cards,
|
|
1447
|
-
}),
|
|
1448
|
-
...(!isCarousel && !isEmpty(button) && button.text && (button.url || viberContent.buttonURL) && {
|
|
1449
|
-
button: {
|
|
1450
|
-
text: button.text,
|
|
1451
|
-
url: button.url || viberContent.buttonURL || '',
|
|
1452
|
-
},
|
|
1453
|
-
}),
|
|
1454
|
-
buttonURL: button.url || viberContent.buttonURL || '',
|
|
1425
|
+
accountName: accountName ? [accountName] : [],
|
|
1426
|
+
brandName: accountName ? [accountName] : [],
|
|
1455
1427
|
};
|
|
1456
1428
|
}
|
|
1457
1429
|
|
|
@@ -2002,7 +1974,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2002
1974
|
}
|
|
2003
1975
|
/>
|
|
2004
1976
|
) : (
|
|
2005
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={this.state?.channel?.toUpperCase()} hostName={this.state?.hostName}/>
|
|
1977
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={this.state?.channel?.toUpperCase()} hostName={this.state?.hostName}/>
|
|
2006
1978
|
);
|
|
2007
1979
|
}
|
|
2008
1980
|
|
|
@@ -2136,7 +2108,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2136
2108
|
);
|
|
2137
2109
|
})()
|
|
2138
2110
|
],
|
|
2139
|
-
hoverOption: isArchivedMode ? null : (
|
|
2111
|
+
hoverOption: isArchivedMode || !this.canPerform(PERMISSIONS.CREATIVE_EDIT) ? null : (
|
|
2140
2112
|
<CapButton
|
|
2141
2113
|
className={
|
|
2142
2114
|
this.props.isFullMode
|
|
@@ -2200,56 +2172,47 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2200
2172
|
{![WECHAT, WHATSAPP, ZALO].includes(
|
|
2201
2173
|
this.state.channel.toUpperCase()
|
|
2202
2174
|
) &&
|
|
2203
|
-
!isTraiDltFeature && !template.isArchived && (
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
</CapMenu.Item>
|
|
2210
|
-
)}
|
|
2175
|
+
!isTraiDltFeature && !template.isArchived && this.gatedMenuItem({
|
|
2176
|
+
allowed: this.canPerform(PERMISSIONS.CREATIVE_CREATE),
|
|
2177
|
+
className: `duplicate-${channelLowerCase}`,
|
|
2178
|
+
onClick: () => this.duplicateTemplate(template),
|
|
2179
|
+
label: <FormattedMessage {...messages.duplicateButton} />,
|
|
2180
|
+
})}
|
|
2211
2181
|
{/* Archive/Unarchive menu item (full mode only, not for Zalo, not for WhatsApp/RCS awaiting/pending) */}
|
|
2212
2182
|
{commonUtil.hasCreativesArchivalEnabled() && (() => {
|
|
2213
2183
|
const channelUp = this.state.channel.toUpperCase();
|
|
2214
2184
|
if (!this.isChannelArchiveEligible(channelUp, status, rcsStatus)) return null;
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
);
|
|
2185
|
+
const canArchive = this.canPerform(PERMISSIONS.CREATIVE_DELETE);
|
|
2186
|
+
return !template?.isArchived
|
|
2187
|
+
? this.gatedMenuItem({
|
|
2188
|
+
allowed: canArchive,
|
|
2189
|
+
className: `archive-${channelLowerCase}`,
|
|
2190
|
+
onClick: () => this.handleTemplateArchiveAction({ templateId: template._id, templateName: template.name }),
|
|
2191
|
+
label: <FormattedMessage {...messages.archiveButton} />,
|
|
2192
|
+
})
|
|
2193
|
+
: this.gatedMenuItem({
|
|
2194
|
+
allowed: canArchive,
|
|
2195
|
+
className: `unarchive-${channelLowerCase}`,
|
|
2196
|
+
onClick: () => this.handleTemplateArchiveAction({ templateId: template._id, templateName: template.name, isUnarchive: true }),
|
|
2197
|
+
label: <FormattedMessage {...messages.unarchiveButton} />,
|
|
2198
|
+
});
|
|
2230
2199
|
})()}
|
|
2231
2200
|
{/* Delete/Unmap menu item */}
|
|
2232
2201
|
{(!(
|
|
2233
2202
|
this.state.channel.toUpperCase() === WHATSAPP &&
|
|
2234
2203
|
status === WHATSAPP_STATUSES.approved
|
|
2235
|
-
) && ![ZALO].includes(this.state.channel.toUpperCase())) && (
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
<FormattedMessage {...messages.unMapButton} />
|
|
2248
|
-
) : (
|
|
2249
|
-
<FormattedMessage {...messages.deleteButton} />
|
|
2250
|
-
)}
|
|
2251
|
-
</CapMenu.Item>
|
|
2252
|
-
)}
|
|
2204
|
+
) && ![ZALO].includes(this.state.channel.toUpperCase())) && this.gatedMenuItem({
|
|
2205
|
+
allowed: this.canPerform(PERMISSIONS.CREATIVE_DELETE),
|
|
2206
|
+
className:
|
|
2207
|
+
this.state.channel.toUpperCase() === WECHAT && !template.definition
|
|
2208
|
+
? `unmap-${channelLowerCase}`
|
|
2209
|
+
: `delete-${channelLowerCase}`,
|
|
2210
|
+
onClick: () => this.toggleDeleteTemplateModal(template),
|
|
2211
|
+
label:
|
|
2212
|
+
this.state.channel.toUpperCase() === WECHAT && !template.definition
|
|
2213
|
+
? <FormattedMessage {...messages.unMapButton} />
|
|
2214
|
+
: <FormattedMessage {...messages.deleteButton} />,
|
|
2215
|
+
})}
|
|
2253
2216
|
</CapMenu>
|
|
2254
2217
|
}
|
|
2255
2218
|
>
|
|
@@ -2472,52 +2435,10 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2472
2435
|
image = {},
|
|
2473
2436
|
button = {},
|
|
2474
2437
|
video = {},
|
|
2475
|
-
type = '',
|
|
2476
|
-
cards = [],
|
|
2477
2438
|
} = {},
|
|
2478
2439
|
} = {},
|
|
2479
2440
|
} = template.versions.base;
|
|
2480
|
-
const isViberCarousel = type === VIBER_MEDIA_TYPES.CAROUSEL;
|
|
2481
2441
|
templateData.content = text;
|
|
2482
|
-
if (isViberCarousel) {
|
|
2483
|
-
const previewCards = Array.isArray(cards) ? cards.slice(0, 1) : [];
|
|
2484
|
-
templateData.className = 'viber-carousel-static';
|
|
2485
|
-
templateData.content = (
|
|
2486
|
-
<CapRow className="viber-carousel-static-content">
|
|
2487
|
-
<CapRow className="viber-carousel-static-message-box">
|
|
2488
|
-
<CapLabel type="label1" className="viber-carousel-static-message">
|
|
2489
|
-
{text}
|
|
2490
|
-
</CapLabel>
|
|
2491
|
-
</CapRow>
|
|
2492
|
-
<CapRow className="viber-carousel-static-cards">
|
|
2493
|
-
{previewCards.map((card, cardIdx) => (
|
|
2494
|
-
<CapRow className="viber-carousel-static-card" key={`viber-static-card-${cardIdx}`}>
|
|
2495
|
-
{card?.mediaUrl ? (
|
|
2496
|
-
<CapImage src={card.mediaUrl} className="viber-carousel-static-image" />
|
|
2497
|
-
) : (
|
|
2498
|
-
<CapRow className="viber-carousel-static-image-placeholder" />
|
|
2499
|
-
)}
|
|
2500
|
-
<CapLabel type="label1" className="viber-carousel-static-text">
|
|
2501
|
-
{card?.text}
|
|
2502
|
-
</CapLabel>
|
|
2503
|
-
<CapRow className="viber-carousel-static-buttons">
|
|
2504
|
-
{(Array.isArray(card?.buttons) && card.buttons.length ? card.buttons : [{}, {}]).slice(0, 2).map((carouselButton, btnIdx) => (
|
|
2505
|
-
<CapLabel
|
|
2506
|
-
key={`viber-static-btn-${cardIdx}-${btnIdx}`}
|
|
2507
|
-
type="label1"
|
|
2508
|
-
className={`viber-carousel-static-button ${btnIdx === 1 ? 'viber-carousel-static-button-secondary' : ''}`}
|
|
2509
|
-
>
|
|
2510
|
-
{(carouselButton?.title || '').trim()}
|
|
2511
|
-
</CapLabel>
|
|
2512
|
-
))}
|
|
2513
|
-
</CapRow>
|
|
2514
|
-
</CapRow>
|
|
2515
|
-
))}
|
|
2516
|
-
</CapRow>
|
|
2517
|
-
</CapRow>
|
|
2518
|
-
);
|
|
2519
|
-
break;
|
|
2520
|
-
}
|
|
2521
2442
|
if (!isEmpty(image)) {
|
|
2522
2443
|
const { url = '' } = image;
|
|
2523
2444
|
templateData.url = url;
|
|
@@ -2820,16 +2741,18 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2820
2741
|
<CapLabel type="label2">
|
|
2821
2742
|
{this.props.intl.formatMessage(messages.templatesSelected, { count: selectedCountLocal })}
|
|
2822
2743
|
</CapLabel>
|
|
2823
|
-
<
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
<
|
|
2831
|
-
|
|
2832
|
-
|
|
2744
|
+
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_DELETE)}>
|
|
2745
|
+
<CapButton
|
|
2746
|
+
type="primary"
|
|
2747
|
+
className="archive-template-button"
|
|
2748
|
+
prefix={<CapIcon type="archive" size="s" />}
|
|
2749
|
+
onClick={() => this.handleBulkArchiveAction({ ids: selectedIdsArrayLocal, count: selectedCountLocal, isUnarchive: isArchivedModeLocal })}
|
|
2750
|
+
>
|
|
2751
|
+
<span className="archive-btn-label">
|
|
2752
|
+
<FormattedMessage {...(isArchivedModeLocal ? messages.unarchiveButton : messages.archiveButton)} />
|
|
2753
|
+
</span>
|
|
2754
|
+
</CapButton>
|
|
2755
|
+
</NoPermissionWrapper>
|
|
2833
2756
|
<CapButton type="secondary" onClick={() => this.props.actions.clearTemplateSelection()}>
|
|
2834
2757
|
<FormattedMessage {...messages.archiveConfirmCancel} />
|
|
2835
2758
|
</CapButton>
|
|
@@ -2868,14 +2791,14 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2868
2791
|
</div> : (
|
|
2869
2792
|
showIllustrationForChannel(MOBILE_PUSH_LOWERCASE) &&
|
|
2870
2793
|
<div style={this.isFullMode() ? { height: "calc(100vh - 325px)", overflow: 'auto'} : {}}>
|
|
2871
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel} hostName={this.state?.hostName}/>
|
|
2794
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel} hostName={this.state?.hostName}/>
|
|
2872
2795
|
</div>
|
|
2873
2796
|
)
|
|
2874
2797
|
}
|
|
2875
2798
|
{(showWhatsappIllustration || showZaloIllustration) && !get(this.props, 'Templates.isArchivedMode', false) && (
|
|
2876
2799
|
noLoaderAndSearchText &&
|
|
2877
2800
|
<div style={this.isFullMode() ? { height: "calc(100vh - 325px)", overflow: 'auto' } : {}}>
|
|
2878
|
-
{noWhatsappZaloTemplates && <ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel} hostName={this.state?.hostName}/>}
|
|
2801
|
+
{noWhatsappZaloTemplates && <ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel} hostName={this.state?.hostName}/>}
|
|
2879
2802
|
{filteredEmptyAndFullModeAs(true) && !isEmpty(templates) && this.state?.hostName && this.whatsappZaloIllustrationText(noFilteredWhatsappZaloTemplatesTitle, noFilteredWhatsappZaloTemplatesDesc)}
|
|
2880
2803
|
{noApprovedWhatsappZaloTemplates && this.whatsappZaloIllustrationText(noApprovedWhatsappZaloTemplatesTitle, showWhatsappIllustration ? noApprovedWhatsappTemplatesDesc : zaloDescIllustration)}
|
|
2881
2804
|
</div>
|
|
@@ -2883,41 +2806,41 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2883
2806
|
}
|
|
2884
2807
|
{showIllustrationForChannel(RCS_LOWERCASE) &&
|
|
2885
2808
|
<div style={this.isFullMode() ? { height: "calc(100vh - 325px)", overflow: 'auto' } : {}}>
|
|
2886
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel}/>
|
|
2809
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel}/>
|
|
2887
2810
|
{filteredEmptyAndFullModeAs(false) && this.whatsappZaloIllustrationText(noApprovedRcsTemplatesTitle, noApprovedRcsTemplatesDesc)}
|
|
2888
2811
|
</div>
|
|
2889
2812
|
}
|
|
2890
2813
|
{showIllustrationForChannel(INAPP_LOWERCASE) &&
|
|
2891
2814
|
<div className={`${this.isFullMode() ? 'inapp-illustration-parent' : ''}`}>
|
|
2892
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel} />
|
|
2815
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel} />
|
|
2893
2816
|
</div>
|
|
2894
2817
|
}
|
|
2895
2818
|
|
|
2896
2819
|
{showIllustrationForChannel(VIBER_LOWERCASE) &&
|
|
2897
2820
|
<div className={`${this.isFullMode() ? 'viber-illustration-parent' : ''}`}>
|
|
2898
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel}/>
|
|
2821
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel}/>
|
|
2899
2822
|
</div>
|
|
2900
2823
|
}
|
|
2901
2824
|
{showIllustrationForChannel(FB_LOWERCASE) &&
|
|
2902
2825
|
<div className={`${this.isFullMode() ? 'fb-illustration-parent' : ''}`}>
|
|
2903
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel}/>
|
|
2826
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel}/>
|
|
2904
2827
|
</div>
|
|
2905
2828
|
}
|
|
2906
2829
|
{showIllustrationForChannel(LINE_LOWERCASE) &&
|
|
2907
2830
|
<div className={`${this.isFullMode() ? 'line-illustration-parent' : ''}`}>
|
|
2908
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel}/>
|
|
2831
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel}/>
|
|
2909
2832
|
</div>
|
|
2910
2833
|
}
|
|
2911
2834
|
{showIllustrationForChannel(WEBPUSH_LOWERCASE) &&
|
|
2912
2835
|
<div style={this.isFullMode() ? { height: "calc(100vh - 20.3125rem)", overflow: 'auto' } : {}}>
|
|
2913
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel} hostName={this.state?.hostName}/>
|
|
2836
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel} hostName={this.state?.hostName}/>
|
|
2914
2837
|
</div>
|
|
2915
2838
|
}
|
|
2916
2839
|
{get(this.props, 'Templates.isArchivedMode', false) && isEmpty(templates) && !isLoading && !getAllTemplatesInProgress && isEmpty(this.state.searchText) && (
|
|
2917
2840
|
<CapRow className={this.isFullMode() ? 'illustration-scroll-wrapper' : ''}>
|
|
2918
2841
|
<ChannelTypeIllustration
|
|
2919
2842
|
isFullMode={this.props.isFullMode}
|
|
2920
|
-
createTemplate={this.createTemplate}
|
|
2843
|
+
createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)}
|
|
2921
2844
|
currentChannel={currentChannel}
|
|
2922
2845
|
isArchivedMode
|
|
2923
2846
|
/>
|
|
@@ -3226,7 +3149,36 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3226
3149
|
}
|
|
3227
3150
|
};
|
|
3228
3151
|
|
|
3152
|
+
canPerform(code) {
|
|
3153
|
+
return hasPermission(getUserAccess(this.props), code);
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
// RBAC: render a dropdown menu item that stays visible but is grayed out,
|
|
3157
|
+
// unclickable, and shows a permission tooltip when the user lacks access
|
|
3158
|
+
// (instead of hiding it, which leaves an empty/no-op 3-dots menu).
|
|
3159
|
+
gatedMenuItem = ({ allowed, className, onClick, label }) => (
|
|
3160
|
+
<CapMenu.Item
|
|
3161
|
+
className={`${className || ''} ${allowed ? '' : 'rbac-disabled'}`.trim()}
|
|
3162
|
+
onClick={() => { if (allowed) onClick(); }}
|
|
3163
|
+
>
|
|
3164
|
+
{allowed ? label : (
|
|
3165
|
+
<CapTooltip title={<FormattedMessage {...globalMessages.noPermissionAction} />}>
|
|
3166
|
+
<span>{label}</span>
|
|
3167
|
+
</CapTooltip>
|
|
3168
|
+
)}
|
|
3169
|
+
</CapMenu.Item>
|
|
3170
|
+
);
|
|
3171
|
+
|
|
3172
|
+
guardWrite(code) {
|
|
3173
|
+
if (this.canPerform(code)) return true;
|
|
3174
|
+
CapNotification.error({
|
|
3175
|
+
message: this.props.intl.formatMessage(globalMessages.noPermissionAction),
|
|
3176
|
+
});
|
|
3177
|
+
return false;
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3229
3180
|
createTemplate(ev) {
|
|
3181
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_CREATE)) return;
|
|
3230
3182
|
const type = this.props.location.query.type;
|
|
3231
3183
|
const module = this.props.location.query.module ? this.props.location.query.module : 'default';
|
|
3232
3184
|
const channel = this.state.channel.toLowerCase();
|
|
@@ -3470,6 +3422,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3470
3422
|
}
|
|
3471
3423
|
|
|
3472
3424
|
duplicateTemplate(template) {
|
|
3425
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_CREATE)) return;
|
|
3473
3426
|
const duplicateObj = cloneDeep(template);
|
|
3474
3427
|
duplicateObj.name = `${COPY_OF} ${template?.name} ${moment().format('MM-DD-YYYY HH:mm:ss')}`;
|
|
3475
3428
|
delete duplicateObj._id;
|
|
@@ -3576,6 +3529,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3576
3529
|
};
|
|
3577
3530
|
|
|
3578
3531
|
handleEditClick(e, template, modeType, path, options) {
|
|
3532
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_EDIT)) return;
|
|
3579
3533
|
if (template && template.isArchived) {
|
|
3580
3534
|
CapNotification.error({ message: this.props.intl.formatMessage(messages.cannotEditArchivedTemplate) });
|
|
3581
3535
|
return;
|
|
@@ -3751,6 +3705,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3751
3705
|
}
|
|
3752
3706
|
|
|
3753
3707
|
toggleDeleteTemplateModal = (template) => {
|
|
3708
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_DELETE)) return;
|
|
3754
3709
|
this.setState({actionTemplate: template}, () => {
|
|
3755
3710
|
this.setState({showModal: true});
|
|
3756
3711
|
});
|
|
@@ -3784,6 +3739,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3784
3739
|
}
|
|
3785
3740
|
|
|
3786
3741
|
handleBulkArchiveAction = ({ ids, count, isUnarchive = false }) => {
|
|
3742
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_DELETE)) return;
|
|
3787
3743
|
const { intl, actions } = this.props;
|
|
3788
3744
|
const { channel } = this.state;
|
|
3789
3745
|
const title = isUnarchive
|
|
@@ -3803,6 +3759,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3803
3759
|
};
|
|
3804
3760
|
|
|
3805
3761
|
handleTemplateArchiveAction = ({ templateId, templateName, isUnarchive = false }) => {
|
|
3762
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_DELETE)) return;
|
|
3806
3763
|
const { intl, actions } = this.props;
|
|
3807
3764
|
const { channel } = this.state;
|
|
3808
3765
|
const title = isUnarchive
|
|
@@ -4035,35 +3992,43 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4035
3992
|
trigger="click"
|
|
4036
3993
|
content={
|
|
4037
3994
|
<div className="popover-content">
|
|
4038
|
-
|
|
4039
|
-
<
|
|
4040
|
-
{this.
|
|
4041
|
-
|
|
3995
|
+
{this.state.channel !== 'wechat' && !template.isArchived && <div className="popover-action-container">
|
|
3996
|
+
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_CREATE)}>
|
|
3997
|
+
<CapButton type="link" onClick={() => this.duplicateTemplate(template)} className="popover-action">
|
|
3998
|
+
{this.props.intl.formatMessage(messages.duplicateButton)}
|
|
3999
|
+
</CapButton>
|
|
4000
|
+
</NoPermissionWrapper>
|
|
4042
4001
|
</div>}
|
|
4043
4002
|
{commonUtil.hasCreativesArchivalEnabled() && this.props.isFullMode && isTemplateArchiveEligible && !template.isArchived && <div className="popover-action-container">
|
|
4044
|
-
<
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4003
|
+
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_DELETE)}>
|
|
4004
|
+
<CapButton
|
|
4005
|
+
type="link"
|
|
4006
|
+
onClick={() => this.handleTemplateArchiveAction({ templateId: template._id, templateName: template.name })}
|
|
4007
|
+
className="popover-action popover-archive-action"
|
|
4008
|
+
>
|
|
4009
|
+
<CapIcon type="archive" size="s" />
|
|
4010
|
+
<CapLabel.CapLabelInline type="label1">{this.props.intl.formatMessage(messages.archiveButton)}</CapLabel.CapLabelInline>
|
|
4011
|
+
</CapButton>
|
|
4012
|
+
</NoPermissionWrapper>
|
|
4052
4013
|
</div>}
|
|
4053
4014
|
{commonUtil.hasCreativesArchivalEnabled() && this.props.isFullMode && isTemplateArchiveEligible && template.isArchived && <div className="popover-action-container">
|
|
4054
|
-
<
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4015
|
+
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_DELETE)}>
|
|
4016
|
+
<CapButton
|
|
4017
|
+
type="link"
|
|
4018
|
+
onClick={() => this.handleTemplateArchiveAction({ templateId: template._id, templateName: template.name, isUnarchive: true })}
|
|
4019
|
+
className="popover-action popover-archive-action"
|
|
4020
|
+
>
|
|
4021
|
+
<CapIcon type="archive" size="s" />
|
|
4022
|
+
<CapLabel.CapLabelInline type="label1">{this.props.intl.formatMessage(messages.unarchiveButton)}</CapLabel.CapLabelInline>
|
|
4023
|
+
</CapButton>
|
|
4024
|
+
</NoPermissionWrapper>
|
|
4062
4025
|
</div>}
|
|
4063
4026
|
<div className="popover-action-container">
|
|
4064
|
-
<
|
|
4065
|
-
{
|
|
4066
|
-
|
|
4027
|
+
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_DELETE)}>
|
|
4028
|
+
<CapButton type="link" onClick={() => this.toggleDeleteTemplateModal(template)} className="popover-action">
|
|
4029
|
+
{deleteOption}
|
|
4030
|
+
</CapButton>
|
|
4031
|
+
</NoPermissionWrapper>
|
|
4067
4032
|
</div>
|
|
4068
4033
|
</div>
|
|
4069
4034
|
}
|
|
@@ -4665,23 +4630,26 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4665
4630
|
&& !this.props.isFullMode
|
|
4666
4631
|
)
|
|
4667
4632
|
? (
|
|
4668
|
-
<
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4633
|
+
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_CREATE)}>
|
|
4634
|
+
<CapLink
|
|
4635
|
+
onClick={this.openCreativesFullMode}
|
|
4636
|
+
title={formatMessage(messages.createNewTemplateLink)}
|
|
4637
|
+
suffix={<CapIcon type="open-in-new" size="s" />}
|
|
4638
|
+
className="create-new-link"
|
|
4639
|
+
/>
|
|
4640
|
+
</NoPermissionWrapper>
|
|
4674
4641
|
)
|
|
4675
4642
|
: this.state?.channel?.toLowerCase() === ZALO_LOWERCASE || (channelLowerCase === WHATSAPP_LOWERCASE && isEmpty(this.state?.hostName)) ? <></> : (
|
|
4676
|
-
<
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4643
|
+
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_CREATE)}>
|
|
4644
|
+
<CapButton
|
|
4645
|
+
className={`create-new-${channelLowerCase} margin-l-8 margin-b-12`}
|
|
4646
|
+
type={"primary"}
|
|
4647
|
+
disabled={this.isCreateDisabled()}
|
|
4648
|
+
onClick={this.createTemplate}
|
|
4649
|
+
>
|
|
4650
|
+
{ this.props.intl.formatMessage((isTraiDltFeature && channel.toUpperCase() === SMS ) ? messages.uploadTemplate : messages.createNewActionButton) }
|
|
4651
|
+
</CapButton>
|
|
4652
|
+
</NoPermissionWrapper>
|
|
4685
4653
|
);
|
|
4686
4654
|
|
|
4687
4655
|
const isWhatsappCountExeeded = templatesCount >= MAX_WHATSAPP_TEMPLATES;
|
|
@@ -5138,22 +5106,15 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
5138
5106
|
? this.props.Templates.selectedWhatsappAccount.name
|
|
5139
5107
|
: null
|
|
5140
5108
|
}
|
|
5141
|
-
// Pass formData for
|
|
5109
|
+
// Pass formData for EMAIL and SMS channels for tag extraction
|
|
5142
5110
|
formData={
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
return previewContent.formData || null;
|
|
5151
|
-
}
|
|
5152
|
-
if (channelUpper === VIBER) {
|
|
5153
|
-
return previewContent;
|
|
5154
|
-
}
|
|
5155
|
-
return null;
|
|
5156
|
-
})()
|
|
5111
|
+
this.state.testAndPreviewContent &&
|
|
5112
|
+
(this.state.channel?.toUpperCase() === EMAIL ||
|
|
5113
|
+
this.state.channel?.toUpperCase() === SMS) &&
|
|
5114
|
+
typeof this.state.testAndPreviewContent === 'object' &&
|
|
5115
|
+
this.state.testAndPreviewContent?.formData
|
|
5116
|
+
? this.state.testAndPreviewContent.formData
|
|
5117
|
+
: null
|
|
5157
5118
|
}
|
|
5158
5119
|
/>
|
|
5159
5120
|
)}
|
|
@@ -3,6 +3,13 @@ jest.mock('@capillarytech/cap-ui-library', () => ({
|
|
|
3
3
|
CapIllustration: ({ children, ...props }) => <div data-testid="cap-illustration" {...props}>{children}</div>
|
|
4
4
|
}));
|
|
5
5
|
|
|
6
|
+
// The component uses useIntl() (context); this shallow test supplies intl via props, so
|
|
7
|
+
// stub useIntl. FormattedMessage stays real.
|
|
8
|
+
jest.mock('react-intl', () => ({
|
|
9
|
+
...jest.requireActual('react-intl'),
|
|
10
|
+
useIntl: () => ({ formatMessage: m => (m && m.defaultMessage) || '' }),
|
|
11
|
+
}));
|
|
12
|
+
|
|
6
13
|
// Mock all image imports
|
|
7
14
|
jest.mock('../../Assets/images/emailIllustration.svg', () => 'test-file-stub');
|
|
8
15
|
jest.mock('../../Assets/images/smsIllustration.svg', () => 'test-file-stub');
|