@capillarytech/creatives-library 9.0.35 → 9.0.36-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/ViberCarouselPreviewCards.js +132 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +108 -15
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +139 -1
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_viberCarouselPreviewCards.scss +131 -0
- package/v2Components/CommonTestAndPreview/index.js +240 -26
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +364 -0
- 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/tests/__snapshots__/index.test.js.snap +118 -118
- 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 +13 -2
- package/v2Containers/Templates/_templates.scss +201 -0
- package/v2Containers/Templates/index.js +244 -110
- package/v2Containers/Templates/tests/ChannelTypeIllustration.test.js +1 -1
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +174 -121
- package/v2Containers/TemplatesV2/index.js +16 -5
- package/v2Containers/Viber/constants.js +21 -0
- package/v2Containers/Viber/index.js +719 -49
- package/v2Containers/Viber/index.scss +175 -0
- package/v2Containers/Viber/messages.js +121 -0
- package/v2Containers/Viber/tests/index.test.js +80 -0
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +268 -268
|
@@ -73,6 +73,7 @@ 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';
|
|
76
77
|
import * as facebookActions from '../Facebook/actions';
|
|
77
78
|
import * as whatsappActions from '../Whatsapp/actions';
|
|
78
79
|
import * as rcsActions from '../Rcs/actions';
|
|
@@ -97,6 +98,9 @@ import WhatsappStatusContainer from '../../v2Components/WhatsappStatusContainer'
|
|
|
97
98
|
import WechatRichmediaTemplatePreview from '../../v2Components/TemplatePreview/WechatRichmediaTemplatePreview';
|
|
98
99
|
import CapCustomSkeleton from '../../v2Components/CapCustomSkeleton';
|
|
99
100
|
import ChannelTypeIllustration from './ChannelTypeIllustration';
|
|
101
|
+
import NoPermissionWrapper from '../../v2Components/NoPermissionWrapper';
|
|
102
|
+
import { getUserAccess, hasPermission, PERMISSIONS } from '../../utils/permissions';
|
|
103
|
+
|
|
100
104
|
import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
|
|
101
105
|
import creativesMessages from '../CreativesContainer/messages';
|
|
102
106
|
import {
|
|
@@ -1396,6 +1400,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
1396
1400
|
const image = viberContent.image || {};
|
|
1397
1401
|
const video = viberContent.video || {};
|
|
1398
1402
|
const button = viberContent.button || {};
|
|
1403
|
+
const messageType = viberContent.type || '';
|
|
1404
|
+
const cardsRaw = viberContent.cards;
|
|
1405
|
+
const cards = Array.isArray(cardsRaw) ? cardsRaw : [];
|
|
1406
|
+
const isCarousel =
|
|
1407
|
+
messageType === VIBER_MEDIA_TYPES.CAROUSEL || cards.length > 0;
|
|
1399
1408
|
|
|
1400
1409
|
const viberPreview = {
|
|
1401
1410
|
messageContent: text,
|
|
@@ -1413,14 +1422,39 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
1413
1422
|
};
|
|
1414
1423
|
}
|
|
1415
1424
|
|
|
1425
|
+
if (isCarousel) {
|
|
1426
|
+
viberPreview.type = VIBER_MEDIA_TYPES.CAROUSEL;
|
|
1427
|
+
viberPreview.cards = cards.map((card) => ({
|
|
1428
|
+
text: card?.text || '',
|
|
1429
|
+
mediaUrl: card?.mediaUrl || '',
|
|
1430
|
+
buttons: (card?.buttons || []).map((btn) => ({
|
|
1431
|
+
title: btn?.title || '',
|
|
1432
|
+
action: btn?.action || '',
|
|
1433
|
+
})),
|
|
1434
|
+
}));
|
|
1435
|
+
viberPreview.showCarouselEditorPreview = true;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1416
1438
|
// Extract account name
|
|
1417
1439
|
const accountName = get(template, 'definition.accountName', '');
|
|
1418
1440
|
|
|
1419
1441
|
// Return Viber content object (same as Viber/index.js getTemplateContent)
|
|
1420
1442
|
return {
|
|
1421
1443
|
viberPreviewContent: viberPreview,
|
|
1422
|
-
accountName: accountName
|
|
1423
|
-
brandName: accountName
|
|
1444
|
+
accountName: accountName || '',
|
|
1445
|
+
brandName: accountName || '',
|
|
1446
|
+
messageContent: text,
|
|
1447
|
+
...(isCarousel && {
|
|
1448
|
+
type: VIBER_MEDIA_TYPES.CAROUSEL,
|
|
1449
|
+
cards: viberPreview.cards,
|
|
1450
|
+
}),
|
|
1451
|
+
...(!isCarousel && !isEmpty(button) && button.text && (button.url || viberContent.buttonURL) && {
|
|
1452
|
+
button: {
|
|
1453
|
+
text: button.text,
|
|
1454
|
+
url: button.url || viberContent.buttonURL || '',
|
|
1455
|
+
},
|
|
1456
|
+
}),
|
|
1457
|
+
buttonURL: button.url || viberContent.buttonURL || '',
|
|
1424
1458
|
};
|
|
1425
1459
|
}
|
|
1426
1460
|
|
|
@@ -1971,7 +2005,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
1971
2005
|
}
|
|
1972
2006
|
/>
|
|
1973
2007
|
) : (
|
|
1974
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={this.state?.channel?.toUpperCase()} hostName={this.state?.hostName}/>
|
|
2008
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={this.state?.channel?.toUpperCase()} hostName={this.state?.hostName}/>
|
|
1975
2009
|
);
|
|
1976
2010
|
}
|
|
1977
2011
|
|
|
@@ -2105,7 +2139,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2105
2139
|
);
|
|
2106
2140
|
})()
|
|
2107
2141
|
],
|
|
2108
|
-
hoverOption: isArchivedMode ? null : (
|
|
2142
|
+
hoverOption: isArchivedMode || !this.canPerform(PERMISSIONS.CREATIVE_EDIT) ? null : (
|
|
2109
2143
|
<CapButton
|
|
2110
2144
|
className={
|
|
2111
2145
|
this.props.isFullMode
|
|
@@ -2169,56 +2203,47 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2169
2203
|
{![WECHAT, WHATSAPP, ZALO].includes(
|
|
2170
2204
|
this.state.channel.toUpperCase()
|
|
2171
2205
|
) &&
|
|
2172
|
-
!isTraiDltFeature && !template.isArchived && (
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
</CapMenu.Item>
|
|
2179
|
-
)}
|
|
2206
|
+
!isTraiDltFeature && !template.isArchived && this.gatedMenuItem({
|
|
2207
|
+
allowed: this.canPerform(PERMISSIONS.CREATIVE_CREATE),
|
|
2208
|
+
className: `duplicate-${channelLowerCase}`,
|
|
2209
|
+
onClick: () => this.duplicateTemplate(template),
|
|
2210
|
+
label: <FormattedMessage {...messages.duplicateButton} />,
|
|
2211
|
+
})}
|
|
2180
2212
|
{/* Archive/Unarchive menu item (full mode only, not for Zalo, not for WhatsApp/RCS awaiting/pending) */}
|
|
2181
2213
|
{commonUtil.hasCreativesArchivalEnabled() && (() => {
|
|
2182
2214
|
const channelUp = this.state.channel.toUpperCase();
|
|
2183
2215
|
if (!this.isChannelArchiveEligible(channelUp, status, rcsStatus)) return null;
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
);
|
|
2216
|
+
const canArchive = this.canPerform(PERMISSIONS.CREATIVE_DELETE);
|
|
2217
|
+
return !template?.isArchived
|
|
2218
|
+
? this.gatedMenuItem({
|
|
2219
|
+
allowed: canArchive,
|
|
2220
|
+
className: `archive-${channelLowerCase}`,
|
|
2221
|
+
onClick: () => this.handleTemplateArchiveAction({ templateId: template._id, templateName: template.name }),
|
|
2222
|
+
label: <FormattedMessage {...messages.archiveButton} />,
|
|
2223
|
+
})
|
|
2224
|
+
: this.gatedMenuItem({
|
|
2225
|
+
allowed: canArchive,
|
|
2226
|
+
className: `unarchive-${channelLowerCase}`,
|
|
2227
|
+
onClick: () => this.handleTemplateArchiveAction({ templateId: template._id, templateName: template.name, isUnarchive: true }),
|
|
2228
|
+
label: <FormattedMessage {...messages.unarchiveButton} />,
|
|
2229
|
+
});
|
|
2199
2230
|
})()}
|
|
2200
2231
|
{/* Delete/Unmap menu item */}
|
|
2201
2232
|
{(!(
|
|
2202
2233
|
this.state.channel.toUpperCase() === WHATSAPP &&
|
|
2203
2234
|
status === WHATSAPP_STATUSES.approved
|
|
2204
|
-
) && ![ZALO].includes(this.state.channel.toUpperCase())) && (
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
<FormattedMessage {...messages.unMapButton} />
|
|
2217
|
-
) : (
|
|
2218
|
-
<FormattedMessage {...messages.deleteButton} />
|
|
2219
|
-
)}
|
|
2220
|
-
</CapMenu.Item>
|
|
2221
|
-
)}
|
|
2235
|
+
) && ![ZALO].includes(this.state.channel.toUpperCase())) && this.gatedMenuItem({
|
|
2236
|
+
allowed: this.canPerform(PERMISSIONS.CREATIVE_DELETE),
|
|
2237
|
+
className:
|
|
2238
|
+
this.state.channel.toUpperCase() === WECHAT && !template.definition
|
|
2239
|
+
? `unmap-${channelLowerCase}`
|
|
2240
|
+
: `delete-${channelLowerCase}`,
|
|
2241
|
+
onClick: () => this.toggleDeleteTemplateModal(template),
|
|
2242
|
+
label:
|
|
2243
|
+
this.state.channel.toUpperCase() === WECHAT && !template.definition
|
|
2244
|
+
? <FormattedMessage {...messages.unMapButton} />
|
|
2245
|
+
: <FormattedMessage {...messages.deleteButton} />,
|
|
2246
|
+
})}
|
|
2222
2247
|
</CapMenu>
|
|
2223
2248
|
}
|
|
2224
2249
|
>
|
|
@@ -2441,10 +2466,65 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2441
2466
|
image = {},
|
|
2442
2467
|
button = {},
|
|
2443
2468
|
video = {},
|
|
2469
|
+
type = '',
|
|
2470
|
+
cards = [],
|
|
2444
2471
|
} = {},
|
|
2445
2472
|
} = {},
|
|
2446
2473
|
} = template.versions.base;
|
|
2474
|
+
const isViberCarousel = type === VIBER_MEDIA_TYPES.CAROUSEL;
|
|
2447
2475
|
templateData.content = text;
|
|
2476
|
+
if (isViberCarousel) {
|
|
2477
|
+
const previewCards = Array.isArray(cards) ? cards.slice(0, 1) : [];
|
|
2478
|
+
// Bypass CapCustomCard Viber Meta + line-clamp (clips title/buttons for tall images).
|
|
2479
|
+
// Keep VIBER in className for listing styles; cardType hits the default content path.
|
|
2480
|
+
templateData.cardType = 'VIBER_CAROUSEL_PREVIEW';
|
|
2481
|
+
templateData.className = 'viber-carousel-static VIBER';
|
|
2482
|
+
templateData.style = {
|
|
2483
|
+
height: 'auto',
|
|
2484
|
+
minHeight: '21.125rem',
|
|
2485
|
+
overflow: 'visible',
|
|
2486
|
+
};
|
|
2487
|
+
templateData.bodyStyle = {
|
|
2488
|
+
height: 'auto',
|
|
2489
|
+
overflow: 'visible',
|
|
2490
|
+
paddingBottom: '12px',
|
|
2491
|
+
};
|
|
2492
|
+
templateData.content = (
|
|
2493
|
+
<CapRow vertical className="viber-carousel-static-content">
|
|
2494
|
+
<CapRow className="viber-carousel-static-message-box">
|
|
2495
|
+
<CapLabel type="label1" className="viber-carousel-static-message">
|
|
2496
|
+
{text}
|
|
2497
|
+
</CapLabel>
|
|
2498
|
+
</CapRow>
|
|
2499
|
+
<CapRow className="viber-carousel-static-cards">
|
|
2500
|
+
{previewCards.map((card, cardIdx) => (
|
|
2501
|
+
<CapRow vertical className="viber-carousel-static-card" key={`viber-static-card-${cardIdx}`}>
|
|
2502
|
+
{card?.mediaUrl ? (
|
|
2503
|
+
<CapImage src={card.mediaUrl} className="viber-carousel-static-image" />
|
|
2504
|
+
) : (
|
|
2505
|
+
<CapRow className="viber-carousel-static-image-placeholder" />
|
|
2506
|
+
)}
|
|
2507
|
+
<CapLabel type="label1" className="viber-carousel-static-text">
|
|
2508
|
+
{card?.text}
|
|
2509
|
+
</CapLabel>
|
|
2510
|
+
<CapRow vertical className="viber-carousel-static-buttons">
|
|
2511
|
+
{(Array.isArray(card?.buttons) && card.buttons.length ? card.buttons : [{}, {}]).slice(0, 2).map((carouselButton, btnIdx) => (
|
|
2512
|
+
<CapLabel
|
|
2513
|
+
key={`viber-static-btn-${cardIdx}-${btnIdx}`}
|
|
2514
|
+
type="label1"
|
|
2515
|
+
className={`viber-carousel-static-button ${btnIdx === 1 ? 'viber-carousel-static-button-secondary' : ''}`}
|
|
2516
|
+
>
|
|
2517
|
+
{(carouselButton?.title || '').trim()}
|
|
2518
|
+
</CapLabel>
|
|
2519
|
+
))}
|
|
2520
|
+
</CapRow>
|
|
2521
|
+
</CapRow>
|
|
2522
|
+
))}
|
|
2523
|
+
</CapRow>
|
|
2524
|
+
</CapRow>
|
|
2525
|
+
);
|
|
2526
|
+
break;
|
|
2527
|
+
}
|
|
2448
2528
|
if (!isEmpty(image)) {
|
|
2449
2529
|
const { url = '' } = image;
|
|
2450
2530
|
templateData.url = url;
|
|
@@ -2747,16 +2827,18 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2747
2827
|
<CapLabel type="label2">
|
|
2748
2828
|
{this.props.intl.formatMessage(messages.templatesSelected, { count: selectedCountLocal })}
|
|
2749
2829
|
</CapLabel>
|
|
2750
|
-
<
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
<
|
|
2758
|
-
|
|
2759
|
-
|
|
2830
|
+
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_DELETE)}>
|
|
2831
|
+
<CapButton
|
|
2832
|
+
type="primary"
|
|
2833
|
+
className="archive-template-button"
|
|
2834
|
+
prefix={<CapIcon type="archive" size="s" />}
|
|
2835
|
+
onClick={() => this.handleBulkArchiveAction({ ids: selectedIdsArrayLocal, count: selectedCountLocal, isUnarchive: isArchivedModeLocal })}
|
|
2836
|
+
>
|
|
2837
|
+
<span className="archive-btn-label">
|
|
2838
|
+
<FormattedMessage {...(isArchivedModeLocal ? messages.unarchiveButton : messages.archiveButton)} />
|
|
2839
|
+
</span>
|
|
2840
|
+
</CapButton>
|
|
2841
|
+
</NoPermissionWrapper>
|
|
2760
2842
|
<CapButton type="secondary" onClick={() => this.props.actions.clearTemplateSelection()}>
|
|
2761
2843
|
<FormattedMessage {...messages.archiveConfirmCancel} />
|
|
2762
2844
|
</CapButton>
|
|
@@ -2795,14 +2877,14 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2795
2877
|
</div> : (
|
|
2796
2878
|
showIllustrationForChannel(MOBILE_PUSH_LOWERCASE) &&
|
|
2797
2879
|
<div style={this.isFullMode() ? { height: "calc(100vh - 325px)", overflow: 'auto'} : {}}>
|
|
2798
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel} hostName={this.state?.hostName}/>
|
|
2880
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel} hostName={this.state?.hostName}/>
|
|
2799
2881
|
</div>
|
|
2800
2882
|
)
|
|
2801
2883
|
}
|
|
2802
2884
|
{(showWhatsappIllustration || showZaloIllustration) && !get(this.props, 'Templates.isArchivedMode', false) && (
|
|
2803
2885
|
noLoaderAndSearchText &&
|
|
2804
2886
|
<div style={this.isFullMode() ? { height: "calc(100vh - 325px)", overflow: 'auto' } : {}}>
|
|
2805
|
-
{noWhatsappZaloTemplates && <ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel} hostName={this.state?.hostName}/>}
|
|
2887
|
+
{noWhatsappZaloTemplates && <ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel} hostName={this.state?.hostName}/>}
|
|
2806
2888
|
{filteredEmptyAndFullModeAs(true) && !isEmpty(templates) && this.state?.hostName && this.whatsappZaloIllustrationText(noFilteredWhatsappZaloTemplatesTitle, noFilteredWhatsappZaloTemplatesDesc)}
|
|
2807
2889
|
{noApprovedWhatsappZaloTemplates && this.whatsappZaloIllustrationText(noApprovedWhatsappZaloTemplatesTitle, showWhatsappIllustration ? noApprovedWhatsappTemplatesDesc : zaloDescIllustration)}
|
|
2808
2890
|
</div>
|
|
@@ -2810,41 +2892,41 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2810
2892
|
}
|
|
2811
2893
|
{showIllustrationForChannel(RCS_LOWERCASE) &&
|
|
2812
2894
|
<div style={this.isFullMode() ? { height: "calc(100vh - 325px)", overflow: 'auto' } : {}}>
|
|
2813
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel}/>
|
|
2895
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel}/>
|
|
2814
2896
|
{filteredEmptyAndFullModeAs(false) && this.whatsappZaloIllustrationText(noApprovedRcsTemplatesTitle, noApprovedRcsTemplatesDesc)}
|
|
2815
2897
|
</div>
|
|
2816
2898
|
}
|
|
2817
2899
|
{showIllustrationForChannel(INAPP_LOWERCASE) &&
|
|
2818
2900
|
<div className={`${this.isFullMode() ? 'inapp-illustration-parent' : ''}`}>
|
|
2819
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel} />
|
|
2901
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel} />
|
|
2820
2902
|
</div>
|
|
2821
2903
|
}
|
|
2822
2904
|
|
|
2823
2905
|
{showIllustrationForChannel(VIBER_LOWERCASE) &&
|
|
2824
2906
|
<div className={`${this.isFullMode() ? 'viber-illustration-parent' : ''}`}>
|
|
2825
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel}/>
|
|
2907
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel}/>
|
|
2826
2908
|
</div>
|
|
2827
2909
|
}
|
|
2828
2910
|
{showIllustrationForChannel(FB_LOWERCASE) &&
|
|
2829
2911
|
<div className={`${this.isFullMode() ? 'fb-illustration-parent' : ''}`}>
|
|
2830
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel}/>
|
|
2912
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel}/>
|
|
2831
2913
|
</div>
|
|
2832
2914
|
}
|
|
2833
2915
|
{showIllustrationForChannel(LINE_LOWERCASE) &&
|
|
2834
2916
|
<div className={`${this.isFullMode() ? 'line-illustration-parent' : ''}`}>
|
|
2835
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel}/>
|
|
2917
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel}/>
|
|
2836
2918
|
</div>
|
|
2837
2919
|
}
|
|
2838
2920
|
{showIllustrationForChannel(WEBPUSH_LOWERCASE) &&
|
|
2839
2921
|
<div style={this.isFullMode() ? { height: "calc(100vh - 20.3125rem)", overflow: 'auto' } : {}}>
|
|
2840
|
-
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} currentChannel={currentChannel} hostName={this.state?.hostName}/>
|
|
2922
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel} hostName={this.state?.hostName}/>
|
|
2841
2923
|
</div>
|
|
2842
2924
|
}
|
|
2843
2925
|
{get(this.props, 'Templates.isArchivedMode', false) && isEmpty(templates) && !isLoading && !getAllTemplatesInProgress && isEmpty(this.state.searchText) && (
|
|
2844
2926
|
<CapRow className={this.isFullMode() ? 'illustration-scroll-wrapper' : ''}>
|
|
2845
2927
|
<ChannelTypeIllustration
|
|
2846
2928
|
isFullMode={this.props.isFullMode}
|
|
2847
|
-
createTemplate={this.createTemplate}
|
|
2929
|
+
createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)}
|
|
2848
2930
|
currentChannel={currentChannel}
|
|
2849
2931
|
isArchivedMode
|
|
2850
2932
|
/>
|
|
@@ -3153,7 +3235,36 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3153
3235
|
}
|
|
3154
3236
|
};
|
|
3155
3237
|
|
|
3238
|
+
canPerform(code) {
|
|
3239
|
+
return hasPermission(getUserAccess(this.props), code);
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
// RBAC: render a dropdown menu item that stays visible but is grayed out,
|
|
3243
|
+
// unclickable, and shows a permission tooltip when the user lacks access
|
|
3244
|
+
// (instead of hiding it, which leaves an empty/no-op 3-dots menu).
|
|
3245
|
+
gatedMenuItem = ({ allowed, className, onClick, label }) => (
|
|
3246
|
+
<CapMenu.Item
|
|
3247
|
+
className={`${className || ''} ${allowed ? '' : 'rbac-disabled'}`.trim()}
|
|
3248
|
+
onClick={() => { if (allowed) onClick(); }}
|
|
3249
|
+
>
|
|
3250
|
+
{allowed ? label : (
|
|
3251
|
+
<CapTooltip title={<FormattedMessage {...globalMessages.noPermissionAction} />}>
|
|
3252
|
+
<span>{label}</span>
|
|
3253
|
+
</CapTooltip>
|
|
3254
|
+
)}
|
|
3255
|
+
</CapMenu.Item>
|
|
3256
|
+
);
|
|
3257
|
+
|
|
3258
|
+
guardWrite(code) {
|
|
3259
|
+
if (this.canPerform(code)) return true;
|
|
3260
|
+
CapNotification.error({
|
|
3261
|
+
message: this.props.intl.formatMessage(globalMessages.noPermissionAction),
|
|
3262
|
+
});
|
|
3263
|
+
return false;
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3156
3266
|
createTemplate(ev) {
|
|
3267
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_CREATE)) return;
|
|
3157
3268
|
const type = this.props.location.query.type;
|
|
3158
3269
|
const module = this.props.location.query.module ? this.props.location.query.module : 'default';
|
|
3159
3270
|
const channel = this.state.channel.toLowerCase();
|
|
@@ -3397,6 +3508,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3397
3508
|
}
|
|
3398
3509
|
|
|
3399
3510
|
duplicateTemplate(template) {
|
|
3511
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_CREATE)) return;
|
|
3400
3512
|
const duplicateObj = cloneDeep(template);
|
|
3401
3513
|
duplicateObj.name = `${COPY_OF} ${template?.name} ${moment().format('MM-DD-YYYY HH:mm:ss')}`;
|
|
3402
3514
|
delete duplicateObj._id;
|
|
@@ -3503,6 +3615,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3503
3615
|
};
|
|
3504
3616
|
|
|
3505
3617
|
handleEditClick(e, template, modeType, path, options) {
|
|
3618
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_EDIT)) return;
|
|
3506
3619
|
if (template && template.isArchived) {
|
|
3507
3620
|
CapNotification.error({ message: this.props.intl.formatMessage(messages.cannotEditArchivedTemplate) });
|
|
3508
3621
|
return;
|
|
@@ -3678,6 +3791,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3678
3791
|
}
|
|
3679
3792
|
|
|
3680
3793
|
toggleDeleteTemplateModal = (template) => {
|
|
3794
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_DELETE)) return;
|
|
3681
3795
|
this.setState({actionTemplate: template}, () => {
|
|
3682
3796
|
this.setState({showModal: true});
|
|
3683
3797
|
});
|
|
@@ -3711,6 +3825,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3711
3825
|
}
|
|
3712
3826
|
|
|
3713
3827
|
handleBulkArchiveAction = ({ ids, count, isUnarchive = false }) => {
|
|
3828
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_DELETE)) return;
|
|
3714
3829
|
const { intl, actions } = this.props;
|
|
3715
3830
|
const { channel } = this.state;
|
|
3716
3831
|
const title = isUnarchive
|
|
@@ -3730,6 +3845,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3730
3845
|
};
|
|
3731
3846
|
|
|
3732
3847
|
handleTemplateArchiveAction = ({ templateId, templateName, isUnarchive = false }) => {
|
|
3848
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_DELETE)) return;
|
|
3733
3849
|
const { intl, actions } = this.props;
|
|
3734
3850
|
const { channel } = this.state;
|
|
3735
3851
|
const title = isUnarchive
|
|
@@ -3962,35 +4078,43 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3962
4078
|
trigger="click"
|
|
3963
4079
|
content={
|
|
3964
4080
|
<div className="popover-content">
|
|
3965
|
-
|
|
3966
|
-
<
|
|
3967
|
-
{this.
|
|
3968
|
-
|
|
4081
|
+
{this.state.channel !== 'wechat' && !template.isArchived && <div className="popover-action-container">
|
|
4082
|
+
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_CREATE)}>
|
|
4083
|
+
<CapButton type="link" onClick={() => this.duplicateTemplate(template)} className="popover-action">
|
|
4084
|
+
{this.props.intl.formatMessage(messages.duplicateButton)}
|
|
4085
|
+
</CapButton>
|
|
4086
|
+
</NoPermissionWrapper>
|
|
3969
4087
|
</div>}
|
|
3970
4088
|
{commonUtil.hasCreativesArchivalEnabled() && this.props.isFullMode && isTemplateArchiveEligible && !template.isArchived && <div className="popover-action-container">
|
|
3971
|
-
<
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
4089
|
+
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_DELETE)}>
|
|
4090
|
+
<CapButton
|
|
4091
|
+
type="link"
|
|
4092
|
+
onClick={() => this.handleTemplateArchiveAction({ templateId: template._id, templateName: template.name })}
|
|
4093
|
+
className="popover-action popover-archive-action"
|
|
4094
|
+
>
|
|
4095
|
+
<CapIcon type="archive" size="s" />
|
|
4096
|
+
<CapLabel.CapLabelInline type="label1">{this.props.intl.formatMessage(messages.archiveButton)}</CapLabel.CapLabelInline>
|
|
4097
|
+
</CapButton>
|
|
4098
|
+
</NoPermissionWrapper>
|
|
3979
4099
|
</div>}
|
|
3980
4100
|
{commonUtil.hasCreativesArchivalEnabled() && this.props.isFullMode && isTemplateArchiveEligible && template.isArchived && <div className="popover-action-container">
|
|
3981
|
-
<
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
4101
|
+
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_DELETE)}>
|
|
4102
|
+
<CapButton
|
|
4103
|
+
type="link"
|
|
4104
|
+
onClick={() => this.handleTemplateArchiveAction({ templateId: template._id, templateName: template.name, isUnarchive: true })}
|
|
4105
|
+
className="popover-action popover-archive-action"
|
|
4106
|
+
>
|
|
4107
|
+
<CapIcon type="archive" size="s" />
|
|
4108
|
+
<CapLabel.CapLabelInline type="label1">{this.props.intl.formatMessage(messages.unarchiveButton)}</CapLabel.CapLabelInline>
|
|
4109
|
+
</CapButton>
|
|
4110
|
+
</NoPermissionWrapper>
|
|
3989
4111
|
</div>}
|
|
3990
4112
|
<div className="popover-action-container">
|
|
3991
|
-
<
|
|
3992
|
-
{
|
|
3993
|
-
|
|
4113
|
+
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_DELETE)}>
|
|
4114
|
+
<CapButton type="link" onClick={() => this.toggleDeleteTemplateModal(template)} className="popover-action">
|
|
4115
|
+
{deleteOption}
|
|
4116
|
+
</CapButton>
|
|
4117
|
+
</NoPermissionWrapper>
|
|
3994
4118
|
</div>
|
|
3995
4119
|
</div>
|
|
3996
4120
|
}
|
|
@@ -4592,23 +4716,26 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4592
4716
|
&& !this.props.isFullMode
|
|
4593
4717
|
)
|
|
4594
4718
|
? (
|
|
4595
|
-
<
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4719
|
+
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_CREATE)}>
|
|
4720
|
+
<CapLink
|
|
4721
|
+
onClick={this.openCreativesFullMode}
|
|
4722
|
+
title={formatMessage(messages.createNewTemplateLink)}
|
|
4723
|
+
suffix={<CapIcon type="open-in-new" size="s" />}
|
|
4724
|
+
className="create-new-link"
|
|
4725
|
+
/>
|
|
4726
|
+
</NoPermissionWrapper>
|
|
4601
4727
|
)
|
|
4602
4728
|
: this.state?.channel?.toLowerCase() === ZALO_LOWERCASE || (channelLowerCase === WHATSAPP_LOWERCASE && isEmpty(this.state?.hostName)) ? <></> : (
|
|
4603
|
-
<
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4729
|
+
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_CREATE)}>
|
|
4730
|
+
<CapButton
|
|
4731
|
+
className={`create-new-${channelLowerCase} margin-l-8 margin-b-12`}
|
|
4732
|
+
type={"primary"}
|
|
4733
|
+
disabled={this.isCreateDisabled()}
|
|
4734
|
+
onClick={this.createTemplate}
|
|
4735
|
+
>
|
|
4736
|
+
{ this.props.intl.formatMessage((isTraiDltFeature && channel.toUpperCase() === SMS ) ? messages.uploadTemplate : messages.createNewActionButton) }
|
|
4737
|
+
</CapButton>
|
|
4738
|
+
</NoPermissionWrapper>
|
|
4612
4739
|
);
|
|
4613
4740
|
|
|
4614
4741
|
const isWhatsappCountExeeded = templatesCount >= MAX_WHATSAPP_TEMPLATES;
|
|
@@ -5065,15 +5192,22 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
5065
5192
|
? this.props.Templates.selectedWhatsappAccount.name
|
|
5066
5193
|
: null
|
|
5067
5194
|
}
|
|
5068
|
-
// Pass formData for
|
|
5195
|
+
// Pass formData for tag extraction (EMAIL/SMS use nested formData; VIBER uses content object)
|
|
5069
5196
|
formData={
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5197
|
+
(() => {
|
|
5198
|
+
const previewContent = this.state.testAndPreviewContent;
|
|
5199
|
+
const channelUpper = this.state.channel?.toUpperCase();
|
|
5200
|
+
if (!previewContent || typeof previewContent !== 'object') {
|
|
5201
|
+
return null;
|
|
5202
|
+
}
|
|
5203
|
+
if ([EMAIL, SMS].includes(channelUpper)) {
|
|
5204
|
+
return previewContent.formData || null;
|
|
5205
|
+
}
|
|
5206
|
+
if (channelUpper === VIBER) {
|
|
5207
|
+
return previewContent;
|
|
5208
|
+
}
|
|
5209
|
+
return null;
|
|
5210
|
+
})()
|
|
5077
5211
|
}
|
|
5078
5212
|
/>
|
|
5079
5213
|
)}
|
|
@@ -16,7 +16,7 @@ jest.mock('@capillarytech/cap-ui-library/assets/images/featureUiNotEnabledIllust
|
|
|
16
16
|
import React from 'react';
|
|
17
17
|
import { shallow, configure } from 'enzyme';
|
|
18
18
|
import Adapter from '@cfaester/enzyme-adapter-react-18';
|
|
19
|
-
import ChannelTypeIllustration from '../ChannelTypeIllustration';
|
|
19
|
+
import { ChannelTypeIllustration } from '../ChannelTypeIllustration';
|
|
20
20
|
import { MOBILE_PUSH, SMS, EMAIL, LINE, VIBER, FACEBOOK, WHATSAPP, RCS, ZALO, INAPP } from '../../CreativesContainer/constants';
|
|
21
21
|
|
|
22
22
|
configure({ adapter: new Adapter() });
|