@capillarytech/creatives-library 9.0.37 → 9.0.39
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/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 +46 -73
- package/v2Containers/Rcs/components/CarouselCard.js +6 -9
- package/v2Containers/Rcs/constants.js +2 -7
- package/v2Containers/Rcs/index.js +72 -126
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +5 -43
- package/v2Containers/Rcs/tests/CarouselCard.test.js +1 -2
- 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 +10 -232
- package/v2Containers/Rcs/tests/utils.test.js +0 -17
- package/v2Containers/Rcs/utils.js +12 -51
- 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 +6 -0
- package/v2Containers/Templates/index.js +141 -100
- 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/Whatsapp/tests/__snapshots__/index.test.js.snap +268 -268
|
@@ -2,8 +2,9 @@ import React, { useMemo } from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import zaloillustration from '@capillarytech/cap-ui-library/assets/images/featureUiNotEnabledIllustration.svg';
|
|
4
4
|
import inAppIllustration from '@capillarytech/cap-ui-library/assets/images/featureUiNotEnabledIllustration.svg';
|
|
5
|
-
import { FormattedMessage } from 'react-intl';
|
|
5
|
+
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
|
|
6
6
|
import { CapIllustration } from "@capillarytech/cap-ui-library";
|
|
7
|
+
import globalMessages from '../Cap/messages';
|
|
7
8
|
import emailIllustration from '../Assets/images/emailIllustration.svg';
|
|
8
9
|
import smsIllustration from '../Assets/images/smsIllustration.svg';
|
|
9
10
|
import pushIllustration from '../Assets/images/pushIllustration.svg';
|
|
@@ -80,11 +81,13 @@ const CHANNEL_CONFIG = {
|
|
|
80
81
|
|
|
81
82
|
function ChannelTypeIllustration(props) {
|
|
82
83
|
const {
|
|
84
|
+
intl,
|
|
83
85
|
isFullMode,
|
|
84
86
|
createTemplate,
|
|
85
87
|
currentChannel,
|
|
86
88
|
hostName,
|
|
87
89
|
isArchivedMode,
|
|
90
|
+
canCreate = true,
|
|
88
91
|
} = props;
|
|
89
92
|
|
|
90
93
|
const templateText = useMemo(() => {
|
|
@@ -183,6 +186,11 @@ function ChannelTypeIllustration(props) {
|
|
|
183
186
|
if (config.hasButton) {
|
|
184
187
|
result.buttonLabel = <FormattedMessage {...config.buttonMessage} values={{ template: templateText }} />;
|
|
185
188
|
result.onClick = createTemplate;
|
|
189
|
+
// RBAC: gray out + disable the create button for users without create permission.
|
|
190
|
+
if (!canCreate) {
|
|
191
|
+
result.disabled = true;
|
|
192
|
+
result.buttonTooltip = intl.formatMessage(globalMessages.noPermissionAction);
|
|
193
|
+
}
|
|
186
194
|
}
|
|
187
195
|
|
|
188
196
|
if (config.descriptionMessage) {
|
|
@@ -208,10 +216,13 @@ function ChannelTypeIllustration(props) {
|
|
|
208
216
|
);
|
|
209
217
|
}
|
|
210
218
|
ChannelTypeIllustration.propTypes = {
|
|
219
|
+
intl: intlShape.isRequired,
|
|
211
220
|
isFullMode: PropTypes.bool,
|
|
212
221
|
createTemplate: PropTypes.func.isRequired,
|
|
213
222
|
currentChannel: PropTypes.string,
|
|
214
223
|
hostName: PropTypes.string,
|
|
215
224
|
isArchivedMode: PropTypes.bool,
|
|
225
|
+
canCreate: PropTypes.bool,
|
|
216
226
|
};
|
|
217
|
-
export
|
|
227
|
+
export { ChannelTypeIllustration };
|
|
228
|
+
export default injectIntl(ChannelTypeIllustration);
|
|
@@ -97,6 +97,9 @@ import WhatsappStatusContainer from '../../v2Components/WhatsappStatusContainer'
|
|
|
97
97
|
import WechatRichmediaTemplatePreview from '../../v2Components/TemplatePreview/WechatRichmediaTemplatePreview';
|
|
98
98
|
import CapCustomSkeleton from '../../v2Components/CapCustomSkeleton';
|
|
99
99
|
import ChannelTypeIllustration from './ChannelTypeIllustration';
|
|
100
|
+
import NoPermissionWrapper from '../../v2Components/NoPermissionWrapper';
|
|
101
|
+
import { getUserAccess, hasPermission, PERMISSIONS } from '../../utils/permissions';
|
|
102
|
+
|
|
100
103
|
import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
|
|
101
104
|
import creativesMessages from '../CreativesContainer/messages';
|
|
102
105
|
import {
|
|
@@ -1971,7 +1974,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
1971
1974
|
}
|
|
1972
1975
|
/>
|
|
1973
1976
|
) : (
|
|
1974
|
-
<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}/>
|
|
1975
1978
|
);
|
|
1976
1979
|
}
|
|
1977
1980
|
|
|
@@ -2105,7 +2108,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2105
2108
|
);
|
|
2106
2109
|
})()
|
|
2107
2110
|
],
|
|
2108
|
-
hoverOption: isArchivedMode ? null : (
|
|
2111
|
+
hoverOption: isArchivedMode || !this.canPerform(PERMISSIONS.CREATIVE_EDIT) ? null : (
|
|
2109
2112
|
<CapButton
|
|
2110
2113
|
className={
|
|
2111
2114
|
this.props.isFullMode
|
|
@@ -2169,56 +2172,47 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2169
2172
|
{![WECHAT, WHATSAPP, ZALO].includes(
|
|
2170
2173
|
this.state.channel.toUpperCase()
|
|
2171
2174
|
) &&
|
|
2172
|
-
!isTraiDltFeature && !template.isArchived && (
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
</CapMenu.Item>
|
|
2179
|
-
)}
|
|
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
|
+
})}
|
|
2180
2181
|
{/* Archive/Unarchive menu item (full mode only, not for Zalo, not for WhatsApp/RCS awaiting/pending) */}
|
|
2181
2182
|
{commonUtil.hasCreativesArchivalEnabled() && (() => {
|
|
2182
2183
|
const channelUp = this.state.channel.toUpperCase();
|
|
2183
2184
|
if (!this.isChannelArchiveEligible(channelUp, status, rcsStatus)) return null;
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
);
|
|
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
|
+
});
|
|
2199
2199
|
})()}
|
|
2200
2200
|
{/* Delete/Unmap menu item */}
|
|
2201
2201
|
{(!(
|
|
2202
2202
|
this.state.channel.toUpperCase() === WHATSAPP &&
|
|
2203
2203
|
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
|
-
)}
|
|
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
|
+
})}
|
|
2222
2216
|
</CapMenu>
|
|
2223
2217
|
}
|
|
2224
2218
|
>
|
|
@@ -2747,16 +2741,18 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2747
2741
|
<CapLabel type="label2">
|
|
2748
2742
|
{this.props.intl.formatMessage(messages.templatesSelected, { count: selectedCountLocal })}
|
|
2749
2743
|
</CapLabel>
|
|
2750
|
-
<
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
<
|
|
2758
|
-
|
|
2759
|
-
|
|
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>
|
|
2760
2756
|
<CapButton type="secondary" onClick={() => this.props.actions.clearTemplateSelection()}>
|
|
2761
2757
|
<FormattedMessage {...messages.archiveConfirmCancel} />
|
|
2762
2758
|
</CapButton>
|
|
@@ -2795,14 +2791,14 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2795
2791
|
</div> : (
|
|
2796
2792
|
showIllustrationForChannel(MOBILE_PUSH_LOWERCASE) &&
|
|
2797
2793
|
<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}/>
|
|
2794
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel} hostName={this.state?.hostName}/>
|
|
2799
2795
|
</div>
|
|
2800
2796
|
)
|
|
2801
2797
|
}
|
|
2802
2798
|
{(showWhatsappIllustration || showZaloIllustration) && !get(this.props, 'Templates.isArchivedMode', false) && (
|
|
2803
2799
|
noLoaderAndSearchText &&
|
|
2804
2800
|
<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}/>}
|
|
2801
|
+
{noWhatsappZaloTemplates && <ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel} hostName={this.state?.hostName}/>}
|
|
2806
2802
|
{filteredEmptyAndFullModeAs(true) && !isEmpty(templates) && this.state?.hostName && this.whatsappZaloIllustrationText(noFilteredWhatsappZaloTemplatesTitle, noFilteredWhatsappZaloTemplatesDesc)}
|
|
2807
2803
|
{noApprovedWhatsappZaloTemplates && this.whatsappZaloIllustrationText(noApprovedWhatsappZaloTemplatesTitle, showWhatsappIllustration ? noApprovedWhatsappTemplatesDesc : zaloDescIllustration)}
|
|
2808
2804
|
</div>
|
|
@@ -2810,41 +2806,41 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2810
2806
|
}
|
|
2811
2807
|
{showIllustrationForChannel(RCS_LOWERCASE) &&
|
|
2812
2808
|
<div style={this.isFullMode() ? { height: "calc(100vh - 325px)", overflow: 'auto' } : {}}>
|
|
2813
|
-
<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}/>
|
|
2814
2810
|
{filteredEmptyAndFullModeAs(false) && this.whatsappZaloIllustrationText(noApprovedRcsTemplatesTitle, noApprovedRcsTemplatesDesc)}
|
|
2815
2811
|
</div>
|
|
2816
2812
|
}
|
|
2817
2813
|
{showIllustrationForChannel(INAPP_LOWERCASE) &&
|
|
2818
2814
|
<div className={`${this.isFullMode() ? 'inapp-illustration-parent' : ''}`}>
|
|
2819
|
-
<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} />
|
|
2820
2816
|
</div>
|
|
2821
2817
|
}
|
|
2822
2818
|
|
|
2823
2819
|
{showIllustrationForChannel(VIBER_LOWERCASE) &&
|
|
2824
2820
|
<div className={`${this.isFullMode() ? 'viber-illustration-parent' : ''}`}>
|
|
2825
|
-
<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}/>
|
|
2826
2822
|
</div>
|
|
2827
2823
|
}
|
|
2828
2824
|
{showIllustrationForChannel(FB_LOWERCASE) &&
|
|
2829
2825
|
<div className={`${this.isFullMode() ? 'fb-illustration-parent' : ''}`}>
|
|
2830
|
-
<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}/>
|
|
2831
2827
|
</div>
|
|
2832
2828
|
}
|
|
2833
2829
|
{showIllustrationForChannel(LINE_LOWERCASE) &&
|
|
2834
2830
|
<div className={`${this.isFullMode() ? 'line-illustration-parent' : ''}`}>
|
|
2835
|
-
<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}/>
|
|
2836
2832
|
</div>
|
|
2837
2833
|
}
|
|
2838
2834
|
{showIllustrationForChannel(WEBPUSH_LOWERCASE) &&
|
|
2839
2835
|
<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}/>
|
|
2836
|
+
<ChannelTypeIllustration isFullMode={this.props.isFullMode} createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)} currentChannel={currentChannel} hostName={this.state?.hostName}/>
|
|
2841
2837
|
</div>
|
|
2842
2838
|
}
|
|
2843
2839
|
{get(this.props, 'Templates.isArchivedMode', false) && isEmpty(templates) && !isLoading && !getAllTemplatesInProgress && isEmpty(this.state.searchText) && (
|
|
2844
2840
|
<CapRow className={this.isFullMode() ? 'illustration-scroll-wrapper' : ''}>
|
|
2845
2841
|
<ChannelTypeIllustration
|
|
2846
2842
|
isFullMode={this.props.isFullMode}
|
|
2847
|
-
createTemplate={this.createTemplate}
|
|
2843
|
+
createTemplate={this.createTemplate} canCreate={this.canPerform(PERMISSIONS.CREATIVE_CREATE)}
|
|
2848
2844
|
currentChannel={currentChannel}
|
|
2849
2845
|
isArchivedMode
|
|
2850
2846
|
/>
|
|
@@ -3153,7 +3149,36 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3153
3149
|
}
|
|
3154
3150
|
};
|
|
3155
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
|
+
|
|
3156
3180
|
createTemplate(ev) {
|
|
3181
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_CREATE)) return;
|
|
3157
3182
|
const type = this.props.location.query.type;
|
|
3158
3183
|
const module = this.props.location.query.module ? this.props.location.query.module : 'default';
|
|
3159
3184
|
const channel = this.state.channel.toLowerCase();
|
|
@@ -3397,6 +3422,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3397
3422
|
}
|
|
3398
3423
|
|
|
3399
3424
|
duplicateTemplate(template) {
|
|
3425
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_CREATE)) return;
|
|
3400
3426
|
const duplicateObj = cloneDeep(template);
|
|
3401
3427
|
duplicateObj.name = `${COPY_OF} ${template?.name} ${moment().format('MM-DD-YYYY HH:mm:ss')}`;
|
|
3402
3428
|
delete duplicateObj._id;
|
|
@@ -3503,6 +3529,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3503
3529
|
};
|
|
3504
3530
|
|
|
3505
3531
|
handleEditClick(e, template, modeType, path, options) {
|
|
3532
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_EDIT)) return;
|
|
3506
3533
|
if (template && template.isArchived) {
|
|
3507
3534
|
CapNotification.error({ message: this.props.intl.formatMessage(messages.cannotEditArchivedTemplate) });
|
|
3508
3535
|
return;
|
|
@@ -3678,6 +3705,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3678
3705
|
}
|
|
3679
3706
|
|
|
3680
3707
|
toggleDeleteTemplateModal = (template) => {
|
|
3708
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_DELETE)) return;
|
|
3681
3709
|
this.setState({actionTemplate: template}, () => {
|
|
3682
3710
|
this.setState({showModal: true});
|
|
3683
3711
|
});
|
|
@@ -3711,6 +3739,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3711
3739
|
}
|
|
3712
3740
|
|
|
3713
3741
|
handleBulkArchiveAction = ({ ids, count, isUnarchive = false }) => {
|
|
3742
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_DELETE)) return;
|
|
3714
3743
|
const { intl, actions } = this.props;
|
|
3715
3744
|
const { channel } = this.state;
|
|
3716
3745
|
const title = isUnarchive
|
|
@@ -3730,6 +3759,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3730
3759
|
};
|
|
3731
3760
|
|
|
3732
3761
|
handleTemplateArchiveAction = ({ templateId, templateName, isUnarchive = false }) => {
|
|
3762
|
+
if (!this.guardWrite(PERMISSIONS.CREATIVE_DELETE)) return;
|
|
3733
3763
|
const { intl, actions } = this.props;
|
|
3734
3764
|
const { channel } = this.state;
|
|
3735
3765
|
const title = isUnarchive
|
|
@@ -3962,35 +3992,43 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3962
3992
|
trigger="click"
|
|
3963
3993
|
content={
|
|
3964
3994
|
<div className="popover-content">
|
|
3965
|
-
|
|
3966
|
-
<
|
|
3967
|
-
{this.
|
|
3968
|
-
|
|
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>
|
|
3969
4001
|
</div>}
|
|
3970
4002
|
{commonUtil.hasCreativesArchivalEnabled() && this.props.isFullMode && isTemplateArchiveEligible && !template.isArchived && <div className="popover-action-container">
|
|
3971
|
-
<
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
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>
|
|
3979
4013
|
</div>}
|
|
3980
4014
|
{commonUtil.hasCreativesArchivalEnabled() && this.props.isFullMode && isTemplateArchiveEligible && template.isArchived && <div className="popover-action-container">
|
|
3981
|
-
<
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
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>
|
|
3989
4025
|
</div>}
|
|
3990
4026
|
<div className="popover-action-container">
|
|
3991
|
-
<
|
|
3992
|
-
{
|
|
3993
|
-
|
|
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>
|
|
3994
4032
|
</div>
|
|
3995
4033
|
</div>
|
|
3996
4034
|
}
|
|
@@ -4592,23 +4630,26 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4592
4630
|
&& !this.props.isFullMode
|
|
4593
4631
|
)
|
|
4594
4632
|
? (
|
|
4595
|
-
<
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
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>
|
|
4601
4641
|
)
|
|
4602
4642
|
: this.state?.channel?.toLowerCase() === ZALO_LOWERCASE || (channelLowerCase === WHATSAPP_LOWERCASE && isEmpty(this.state?.hostName)) ? <></> : (
|
|
4603
|
-
<
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
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>
|
|
4612
4653
|
);
|
|
4613
4654
|
|
|
4614
4655
|
const isWhatsappCountExeeded = templatesCount >= MAX_WHATSAPP_TEMPLATES;
|
|
@@ -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() });
|