@capillarytech/creatives-library 8.0.345 → 8.0.347
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/utils/tests/v2Common.test.js +46 -1
- package/utils/v2common.js +18 -0
- package/v2Containers/Email/reducer.js +12 -3
- package/v2Containers/Email/sagas.js +9 -4
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +4 -0
- package/v2Containers/Email/tests/reducer.test.js +47 -0
- package/v2Containers/Email/tests/sagas.test.js +146 -6
- package/v2Containers/Templates/_templates.scss +53 -4
- package/v2Containers/Templates/actions.js +16 -8
- package/v2Containers/Templates/index.js +187 -123
- package/v2Containers/Templates/messages.js +20 -0
- package/v2Containers/Templates/reducer.js +26 -11
- package/v2Containers/Templates/sagas.js +13 -37
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +45 -0
- package/v2Containers/Templates/tests/sagas.test.js +142 -66
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -1
|
@@ -81,7 +81,7 @@ import * as webpushActions from '../WebPush/actions';
|
|
|
81
81
|
import * as globalActions from '../Cap/actions';
|
|
82
82
|
import { makeSelectAuthenticated } from '../Cap/selectors';
|
|
83
83
|
import { UserIsAuthenticated } from '../../utils/authWrapper';
|
|
84
|
-
import { getObjFromQueryParams } from '../../utils/v2common';
|
|
84
|
+
import { getObjFromQueryParams, buildTemplateNameDescription } from '../../utils/v2common';
|
|
85
85
|
import messages from './messages';
|
|
86
86
|
import {checkUnicode} from '../../utils/smsCharCountV2';
|
|
87
87
|
import { containsBase64Images } from '../../utils/content';
|
|
@@ -106,7 +106,7 @@ import {
|
|
|
106
106
|
CREATE,
|
|
107
107
|
} from '../App/constants';
|
|
108
108
|
import {MAX_WHATSAPP_TEMPLATES, WARNING_WHATSAPP_TEMPLATES , ACCOUNT_MAPPING_ON_CHANNEL, noFilteredWhatsappZaloTemplatesTitle, noFilteredWhatsappZaloTemplatesDesc, noApprovedWhatsappZaloTemplatesTitle, noApprovedWhatsappTemplatesDesc, zaloDescIllustration, noApprovedRcsTemplatesTitle, noApprovedRcsTemplatesDesc} from './constants';
|
|
109
|
-
import { COPY_OF } from '../../constants/unified';
|
|
109
|
+
import { COPY_OF, EMBEDDED } from '../../constants/unified';
|
|
110
110
|
import {
|
|
111
111
|
STATUS_OPTIONS,
|
|
112
112
|
CATEGORY,
|
|
@@ -819,6 +819,41 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
819
819
|
this.getAllTemplates({params, resetPage: true});
|
|
820
820
|
}
|
|
821
821
|
|
|
822
|
+
// Archive / Unarchive / Bulk Archive / Bulk Unarchive — detect completion and refresh listing
|
|
823
|
+
const wasArchiving = this.props.Templates.archiveInProgress;
|
|
824
|
+
const isArchiveDone = !nextProps.Templates.archiveInProgress && wasArchiving;
|
|
825
|
+
if (isArchiveDone && !nextProps.Templates.archiveError) {
|
|
826
|
+
const { successMessage, description } = nextProps.Templates.archiveSuccessPayload || {};
|
|
827
|
+
if (successMessage) CapNotification.success({ message: successMessage, description });
|
|
828
|
+
this.getAllTemplates({ params, resetPage: true });
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const wasUnarchiving = this.props.Templates.unarchiveInProgress;
|
|
832
|
+
const isUnarchiveDone = !nextProps.Templates.unarchiveInProgress && wasUnarchiving;
|
|
833
|
+
if (isUnarchiveDone && !nextProps.Templates.unarchiveError) {
|
|
834
|
+
const { successMessage, description } = nextProps.Templates.unarchiveSuccessPayload || {};
|
|
835
|
+
if (successMessage) CapNotification.success({ message: successMessage, description });
|
|
836
|
+
this.getAllTemplates({ params, resetPage: true });
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const wasBulkArchiving = this.props.Templates.bulkArchiveInProgress;
|
|
840
|
+
const isBulkArchiveDone = !nextProps.Templates.bulkArchiveInProgress && wasBulkArchiving;
|
|
841
|
+
if (isBulkArchiveDone && !nextProps.Templates.bulkArchiveError) {
|
|
842
|
+
const { successMessage, count } = nextProps.Templates.bulkArchiveSuccessPayload || {};
|
|
843
|
+
const msg = successMessage ? successMessage(count) : `${count} templates archived successfully`;
|
|
844
|
+
CapNotification.success({ message: msg });
|
|
845
|
+
this.getAllTemplates({ params, resetPage: true });
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const wasBulkUnarchiving = this.props.Templates.bulkUnarchiveInProgress;
|
|
849
|
+
const isBulkUnarchiveDone = !nextProps.Templates.bulkUnarchiveInProgress && wasBulkUnarchiving;
|
|
850
|
+
if (isBulkUnarchiveDone && !nextProps.Templates.bulkUnarchiveError) {
|
|
851
|
+
const { successMessage, count } = nextProps.Templates.bulkUnarchiveSuccessPayload || {};
|
|
852
|
+
const msg = successMessage ? successMessage(count) : `${count} templates unarchived successfully`;
|
|
853
|
+
CapNotification.success({ message: msg });
|
|
854
|
+
this.getAllTemplates({ params, resetPage: true });
|
|
855
|
+
}
|
|
856
|
+
|
|
822
857
|
if (!nextProps.Templates.sendingFile && !isEqual(this.props.Templates.sendingFile, nextProps.Templates.sendingFile) && !nextProps.Templates.errorSendingFile) {
|
|
823
858
|
const module = this.props.location.query.module ? this.props.location.query.module : 'default';
|
|
824
859
|
const isLanguageSupport = (this.props.location.query.isLanguageSupport) ? this.props.location.query.isLanguageSupport : true;
|
|
@@ -1905,58 +1940,60 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
1905
1940
|
// Archive eligibility per template: Zalo never; WhatsApp/RCS not when pending/awaiting
|
|
1906
1941
|
const cardWhatsappStatus = get(template, `versions.base.content.${WHATSAPP_LOWERCASE}.status`, '');
|
|
1907
1942
|
const cardRcsStatus = get(template, 'versions.base.content.RCS.rcsContent.cardContent[0].Status', '');
|
|
1908
|
-
const isCardArchiveEligible = currentChannel
|
|
1909
|
-
|
|
1910
|
-
|
|
1943
|
+
const isCardArchiveEligible = this.isChannelArchiveEligible(currentChannel, cardWhatsappStatus, cardRcsStatus);
|
|
1944
|
+
const isArchivedMode = get(this.props, 'Templates.isArchivedMode', false);
|
|
1945
|
+
const isAnyArchiveInProgress = !!(get(this.props, 'Templates.archiveInProgress') || get(this.props, 'Templates.unarchiveInProgress') || get(this.props, 'Templates.bulkArchiveInProgress') || get(this.props, 'Templates.bulkUnarchiveInProgress'));
|
|
1911
1946
|
const templateData = {
|
|
1912
1947
|
key: `${currentChannel}-card-${template?.name}`,
|
|
1913
1948
|
title: (
|
|
1914
|
-
<span
|
|
1915
|
-
{this.props.isFullMode && this.props.location.query.type !==
|
|
1949
|
+
<span className="template-card-title">
|
|
1950
|
+
{this.props.isFullMode && this.props.location.query.type !== EMBEDDED && isCardArchiveEligible && (
|
|
1916
1951
|
<CapCheckbox
|
|
1917
1952
|
checked={selectedIdsArrayForCard.includes(template._id)}
|
|
1918
|
-
onChange={() => this.props.actions.toggleTemplateSelection(template._id)}
|
|
1953
|
+
onChange={() => !isAnyArchiveInProgress && this.props.actions.toggleTemplateSelection(template._id)}
|
|
1919
1954
|
onClick={(e) => e.stopPropagation()}
|
|
1920
|
-
|
|
1955
|
+
disabled={isAnyArchiveInProgress}
|
|
1921
1956
|
/>
|
|
1922
1957
|
)}
|
|
1923
|
-
{template?.name}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
<
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1958
|
+
<CapLabel.CapLabelInline type="label1" title={template?.name} className="template-card-name">
|
|
1959
|
+
{template?.name}
|
|
1960
|
+
{currentChannel === INAPP && (
|
|
1961
|
+
<CapRow>
|
|
1962
|
+
<CapColoredTag
|
|
1963
|
+
tagColor={INAPP_LAYOUT_DETAILS[inappBodyType]?.tagColor}
|
|
1964
|
+
tagTextColor={
|
|
1965
|
+
INAPP_LAYOUT_DETAILS[inappBodyType]?.tagTextColor
|
|
1966
|
+
}
|
|
1967
|
+
tagHeight="1.25rem"
|
|
1968
|
+
tagFontSize="12px"
|
|
1969
|
+
>
|
|
1970
|
+
{INAPP_LAYOUT_DETAILS[inappBodyType]?.text}
|
|
1971
|
+
</CapColoredTag>
|
|
1972
|
+
</CapRow>
|
|
1973
|
+
)}
|
|
1974
|
+
</CapLabel.CapLabelInline>
|
|
1938
1975
|
</span>
|
|
1939
1976
|
),
|
|
1940
|
-
extra: [
|
|
1977
|
+
extra: isArchivedMode ? [] : [
|
|
1941
1978
|
// Hide preview icon for channels that support Test and Preview
|
|
1942
1979
|
// Show preview icon only for channels that don't support Test and Preview
|
|
1943
1980
|
(() => {
|
|
1944
1981
|
// Channels that have Test and Preview integrated
|
|
1945
1982
|
const testAndPreviewChannels = [EMAIL, SMS, WHATSAPP, RCS, INAPP, MOBILE_PUSH, VIBER, ZALO];
|
|
1946
1983
|
const isTestAndPreviewSupported = testAndPreviewChannels.includes(currentChannel.toUpperCase());
|
|
1947
|
-
|
|
1984
|
+
|
|
1948
1985
|
// Don't show preview icon if channel supports Test and Preview
|
|
1949
1986
|
if (isTestAndPreviewSupported) {
|
|
1950
1987
|
return null;
|
|
1951
1988
|
}
|
|
1952
|
-
|
|
1989
|
+
|
|
1953
1990
|
// Show preview icon for other channels (e.g., WeChat, Line, Facebook, Ebill)
|
|
1954
1991
|
if (currentChannel === ZALO && isZaloPreviewLoading) {
|
|
1955
1992
|
return (
|
|
1956
1993
|
<CapSpin style={{ marginRight: "16px", position: "static", display: "inline" }} spinning />
|
|
1957
1994
|
);
|
|
1958
1995
|
}
|
|
1959
|
-
|
|
1996
|
+
|
|
1960
1997
|
return this.getHoverComponent(
|
|
1961
1998
|
<CapIcon
|
|
1962
1999
|
className={`view-${channelLowerCase}`}
|
|
@@ -1976,7 +2013,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
1976
2013
|
);
|
|
1977
2014
|
})()
|
|
1978
2015
|
],
|
|
1979
|
-
hoverOption: (
|
|
2016
|
+
hoverOption: isArchivedMode ? null : (
|
|
1980
2017
|
<CapButton
|
|
1981
2018
|
className={
|
|
1982
2019
|
this.props.isFullMode
|
|
@@ -2016,8 +2053,8 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2016
2053
|
<CapDropdown
|
|
2017
2054
|
overlay={
|
|
2018
2055
|
<CapMenu>
|
|
2019
|
-
{/* Phase 16: Test and Preview menu item - Show for supported channels */}
|
|
2020
|
-
{(this.isTestAndPreviewSupported() ||
|
|
2056
|
+
{/* Phase 16: Test and Preview menu item - Show for supported channels, hide in archived mode */}
|
|
2057
|
+
{!isArchivedMode && (this.isTestAndPreviewSupported() ||
|
|
2021
2058
|
(this.state.channel.toUpperCase() === WHATSAPP &&
|
|
2022
2059
|
status === WHATSAPP_STATUSES.approved) || (this.state.channel.toUpperCase() === RCS &&
|
|
2023
2060
|
rcsStatus === RCS_STATUSES.approved)) && (
|
|
@@ -2051,34 +2088,18 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2051
2088
|
{/* Archive/Unarchive menu item (full mode only, not for Zalo, not for WhatsApp/RCS awaiting/pending) */}
|
|
2052
2089
|
{(() => {
|
|
2053
2090
|
const channelUp = this.state.channel.toUpperCase();
|
|
2054
|
-
|
|
2055
|
-
!(channelUp === WHATSAPP && [WHATSAPP_STATUSES.awaitingApproval, WHATSAPP_STATUSES.pending, WHATSAPP_STATUSES.unsubmitted].includes(status)) &&
|
|
2056
|
-
!(channelUp === RCS && [RCS_STATUSES.awaitingApproval, RCS_STATUSES.pending].includes(rcsStatus));
|
|
2057
|
-
if (!isArchiveEligible) return null;
|
|
2091
|
+
if (!this.isChannelArchiveEligible(channelUp, status, rcsStatus)) return null;
|
|
2058
2092
|
return !template.isArchived ? (
|
|
2059
2093
|
<CapMenu.Item
|
|
2060
2094
|
className={`archive-${channelLowerCase}`}
|
|
2061
|
-
onClick={() => {
|
|
2062
|
-
this.showArchiveConfirm({
|
|
2063
|
-
title: this.props.intl.formatMessage(messages.archiveTemplates),
|
|
2064
|
-
onConfirm: () => this.props.actions.archiveTemplate(this.state.channel, template._id, template.name),
|
|
2065
|
-
count: 1,
|
|
2066
|
-
});
|
|
2067
|
-
}}
|
|
2095
|
+
onClick={() => this.handleTemplateArchiveAction({ templateId: template._id, templateName: template.name })}
|
|
2068
2096
|
>
|
|
2069
2097
|
<FormattedMessage {...messages.archiveButton} />
|
|
2070
2098
|
</CapMenu.Item>
|
|
2071
2099
|
) : (
|
|
2072
2100
|
<CapMenu.Item
|
|
2073
2101
|
className={`unarchive-${channelLowerCase}`}
|
|
2074
|
-
onClick={() => {
|
|
2075
|
-
this.showArchiveConfirm({
|
|
2076
|
-
title: this.props.intl.formatMessage(messages.unarchiveTemplates),
|
|
2077
|
-
onConfirm: () => this.props.actions.unarchiveTemplate(this.state.channel, template._id, template.name),
|
|
2078
|
-
count: 1,
|
|
2079
|
-
isUnarchive: true,
|
|
2080
|
-
});
|
|
2081
|
-
}}
|
|
2102
|
+
onClick={() => this.handleTemplateArchiveAction({ templateId: template._id, templateName: template.name, isUnarchive: true })}
|
|
2082
2103
|
>
|
|
2083
2104
|
<FormattedMessage {...messages.unarchiveButton} />
|
|
2084
2105
|
</CapMenu.Item>
|
|
@@ -2365,13 +2386,13 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2365
2386
|
const { whatsappImageSrc = '', templateMsg, docPreview, whatsappVideoPreviewImg = '', templateHeaderPreview, templateFooterPreview, carouselData = [] } = getWhatsappContent(template);
|
|
2366
2387
|
templateData.title = (
|
|
2367
2388
|
<CapRow type="flex" align="middle">
|
|
2368
|
-
{this.props.isFullMode && this.props.location.query.type !==
|
|
2389
|
+
{this.props.isFullMode && this.props.location.query.type !== EMBEDDED && isCardArchiveEligible && (
|
|
2369
2390
|
<CapCheckbox
|
|
2370
2391
|
checked={selectedIdsArrayForCard.includes(template._id)}
|
|
2371
|
-
onChange={() => this.props.actions.toggleTemplateSelection(template._id)}
|
|
2392
|
+
onChange={() => !isAnyArchiveInProgress && this.props.actions.toggleTemplateSelection(template._id)}
|
|
2372
2393
|
onClick={(e) => e.stopPropagation()}
|
|
2373
|
-
|
|
2374
|
-
|
|
2394
|
+
disabled={isAnyArchiveInProgress}
|
|
2395
|
+
/>
|
|
2375
2396
|
)}
|
|
2376
2397
|
<CapLabel className="whatsapp-rcs-template-name">{template?.name}</CapLabel>
|
|
2377
2398
|
<WhatsappStatusContainer template={template} />
|
|
@@ -2466,13 +2487,13 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2466
2487
|
const statusDisplay=getRcsStatusType(status);
|
|
2467
2488
|
templateData.title = (
|
|
2468
2489
|
<CapRow type="flex" align="middle">
|
|
2469
|
-
{this.props.isFullMode && this.props.location.query.type !==
|
|
2490
|
+
{this.props.isFullMode && this.props.location.query.type !== EMBEDDED && isCardArchiveEligible && (
|
|
2470
2491
|
<CapCheckbox
|
|
2471
2492
|
checked={selectedIdsArrayForCard.includes(template._id)}
|
|
2472
|
-
onChange={() => this.props.actions.toggleTemplateSelection(template._id)}
|
|
2493
|
+
onChange={() => !isAnyArchiveInProgress && this.props.actions.toggleTemplateSelection(template._id)}
|
|
2473
2494
|
onClick={(e) => e.stopPropagation()}
|
|
2474
|
-
|
|
2475
|
-
|
|
2495
|
+
disabled={isAnyArchiveInProgress}
|
|
2496
|
+
/>
|
|
2476
2497
|
)}
|
|
2477
2498
|
<CapLabel className="whatsapp-rcs-template-name">{name}</CapLabel>
|
|
2478
2499
|
<CapRow type="flex" align="middle" className="rcs-status-container zalo-status-color">
|
|
@@ -2615,6 +2636,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2615
2636
|
const accountId = get(this.props, 'Templates.selectedWeChatAccount.uuid');
|
|
2616
2637
|
const accounts = get(this.props, 'Templates.weCrmAccounts');
|
|
2617
2638
|
const getAllTemplatesInProgress = get(this.props, 'Templates.getAllTemplatesInProgress');
|
|
2639
|
+
const archiveListingRefreshType = get(this.props, 'Templates.archiveListingRefreshType', null);
|
|
2640
|
+
const isArchiveOperationInProgress = get(this.props, 'Templates.archiveInProgress', false)
|
|
2641
|
+
|| get(this.props, 'Templates.unarchiveInProgress', false)
|
|
2642
|
+
|| get(this.props, 'Templates.bulkArchiveInProgress', false)
|
|
2643
|
+
|| get(this.props, 'Templates.bulkUnarchiveInProgress', false);
|
|
2618
2644
|
|
|
2619
2645
|
const noWhatsappZaloTemplates = this.isFullMode() && isEmpty(templates) || !this.state.hostName;
|
|
2620
2646
|
const noApprovedWhatsappZaloTemplates = filteredEmptyAndFullModeAs(false) && !isEmpty(this.state?.hostName);
|
|
@@ -2641,20 +2667,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2641
2667
|
<CapButton
|
|
2642
2668
|
type="primary"
|
|
2643
2669
|
prefix={<CapIcon type="archive" size="l" />}
|
|
2644
|
-
onClick={() => {
|
|
2645
|
-
this.showArchiveConfirm({
|
|
2646
|
-
title: this.props.intl.formatMessage(isArchivedModeLocal ? messages.unarchiveTemplates : messages.archiveTemplates),
|
|
2647
|
-
onConfirm: () => {
|
|
2648
|
-
if (isArchivedModeLocal) {
|
|
2649
|
-
this.props.actions.bulkUnarchiveTemplates(this.state.channel, selectedIdsArrayLocal);
|
|
2650
|
-
} else {
|
|
2651
|
-
this.props.actions.bulkArchiveTemplates(this.state.channel, selectedIdsArrayLocal);
|
|
2652
|
-
}
|
|
2653
|
-
},
|
|
2654
|
-
count: selectedCountLocal,
|
|
2655
|
-
isUnarchive: isArchivedModeLocal,
|
|
2656
|
-
});
|
|
2657
|
-
}}
|
|
2670
|
+
onClick={() => this.handleBulkArchiveAction({ ids: selectedIdsArrayLocal, count: selectedCountLocal, isUnarchive: isArchivedModeLocal })}
|
|
2658
2671
|
>
|
|
2659
2672
|
<span className="archive-btn-label">
|
|
2660
2673
|
<FormattedMessage {...(isArchivedModeLocal ? messages.unarchiveButton : messages.archiveButton)} />
|
|
@@ -2744,17 +2757,26 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2744
2757
|
</div>
|
|
2745
2758
|
}
|
|
2746
2759
|
{get(this.props, 'Templates.isArchivedMode', false) && isEmpty(templates) && !isLoading && !getAllTemplatesInProgress && isEmpty(this.state.searchText) && (
|
|
2747
|
-
<
|
|
2760
|
+
<CapRow className={this.isFullMode() ? 'illustration-scroll-wrapper' : ''}>
|
|
2748
2761
|
<ChannelTypeIllustration
|
|
2749
2762
|
isFullMode={this.props.isFullMode}
|
|
2750
2763
|
createTemplate={this.createTemplate}
|
|
2751
2764
|
currentChannel={currentChannel}
|
|
2752
2765
|
isArchivedMode
|
|
2753
2766
|
/>
|
|
2754
|
-
</
|
|
2767
|
+
</CapRow>
|
|
2755
2768
|
)}
|
|
2756
2769
|
{<CapCustomSkeleton loader={isInitialLoading && (isLoading || getAllTemplatesInProgress)} />}
|
|
2757
|
-
{
|
|
2770
|
+
{!isInitialLoading && getAllTemplatesInProgress && archiveListingRefreshType ? (
|
|
2771
|
+
<div className="archive-listing-spinner">
|
|
2772
|
+
<CapSpin spinning />
|
|
2773
|
+
<CapLabel.CapLabelInline type="label1">
|
|
2774
|
+
{archiveListingRefreshType === 'ARCHIVE' ? 'Archival in progress' : 'Unarchival in progress'}
|
|
2775
|
+
</CapLabel.CapLabelInline>
|
|
2776
|
+
</div>
|
|
2777
|
+
) : (
|
|
2778
|
+
<CapPageSpinner spinning={!isInitialLoading && (isLoading || getAllTemplatesInProgress)} />
|
|
2779
|
+
)}
|
|
2758
2780
|
</div>
|
|
2759
2781
|
}
|
|
2760
2782
|
</div>);
|
|
@@ -3581,6 +3603,48 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3581
3603
|
this.setState({showModal: false});
|
|
3582
3604
|
}
|
|
3583
3605
|
|
|
3606
|
+
handleBulkArchiveAction = ({ ids, count, isUnarchive = false }) => {
|
|
3607
|
+
const { intl, actions } = this.props;
|
|
3608
|
+
const { channel } = this.state;
|
|
3609
|
+
const title = isUnarchive
|
|
3610
|
+
? intl.formatMessage(messages.unarchiveTemplates)
|
|
3611
|
+
: intl.formatMessage(messages.archiveTemplates);
|
|
3612
|
+
const action = isUnarchive ? actions.bulkUnarchiveTemplates : actions.bulkArchiveTemplates;
|
|
3613
|
+
const successMessage = (c) => intl.formatMessage(
|
|
3614
|
+
isUnarchive ? messages.bulkUnarchiveSuccess : messages.bulkArchiveSuccess,
|
|
3615
|
+
{ count: c }
|
|
3616
|
+
);
|
|
3617
|
+
this.showArchiveConfirm({
|
|
3618
|
+
title,
|
|
3619
|
+
count,
|
|
3620
|
+
isUnarchive,
|
|
3621
|
+
onConfirm: () => action(channel, ids, successMessage),
|
|
3622
|
+
});
|
|
3623
|
+
};
|
|
3624
|
+
|
|
3625
|
+
handleTemplateArchiveAction = ({ templateId, templateName, isUnarchive = false }) => {
|
|
3626
|
+
const { intl, actions } = this.props;
|
|
3627
|
+
const { channel } = this.state;
|
|
3628
|
+
const title = isUnarchive
|
|
3629
|
+
? intl.formatMessage(messages.unarchiveTemplates)
|
|
3630
|
+
: intl.formatMessage(messages.archiveTemplates);
|
|
3631
|
+
const successMessage = isUnarchive
|
|
3632
|
+
? intl.formatMessage(messages.unarchiveTemplateSuccess)
|
|
3633
|
+
: intl.formatMessage(messages.archiveTemplateSuccess);
|
|
3634
|
+
const action = isUnarchive ? actions.unarchiveTemplate : actions.archiveTemplate;
|
|
3635
|
+
this.showArchiveConfirm({
|
|
3636
|
+
title,
|
|
3637
|
+
count: 1,
|
|
3638
|
+
isUnarchive,
|
|
3639
|
+
onConfirm: () => action(
|
|
3640
|
+
channel,
|
|
3641
|
+
templateId,
|
|
3642
|
+
successMessage,
|
|
3643
|
+
buildTemplateNameDescription(intl.formatMessage(messages.templateNameLabel), templateName)
|
|
3644
|
+
),
|
|
3645
|
+
});
|
|
3646
|
+
};
|
|
3647
|
+
|
|
3584
3648
|
// Shared helper for archive/unarchive confirm modals:
|
|
3585
3649
|
// - no icon, lighter overlay, Confirm button on left (primary), Cancel on right
|
|
3586
3650
|
showArchiveConfirm = ({ title, content, onConfirm, count = 1, isUnarchive = false }) => {
|
|
@@ -3764,22 +3828,20 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3764
3828
|
const templateWhatsappStatus = get(template, `versions.base.content.${WHATSAPP_LOWERCASE}.status`, '');
|
|
3765
3829
|
const templateRcsStatus = get(template, 'versions.base.content.RCS.rcsContent.cardContent[0].Status', '');
|
|
3766
3830
|
const channelUpCase = this.state.channel.toUpperCase();
|
|
3767
|
-
const isTemplateArchiveEligible = channelUpCase
|
|
3768
|
-
!(channelUpCase === WHATSAPP && [WHATSAPP_STATUSES.awaitingApproval, WHATSAPP_STATUSES.pending, WHATSAPP_STATUSES.unsubmitted].includes(templateWhatsappStatus)) &&
|
|
3769
|
-
!(channelUpCase === RCS && [RCS_STATUSES.awaitingApproval, RCS_STATUSES.pending].includes(templateRcsStatus));
|
|
3831
|
+
const isTemplateArchiveEligible = this.isChannelArchiveEligible(channelUpCase, templateWhatsappStatus, templateRcsStatus);
|
|
3770
3832
|
|
|
3771
3833
|
// Checkbox on card header (full mode only, only for archive-eligible templates)
|
|
3772
|
-
if (this.props.isFullMode && this.props.location.query.type !==
|
|
3834
|
+
if (this.props.isFullMode && this.props.location.query.type !== EMBEDDED && isTemplateArchiveEligible) {
|
|
3773
3835
|
const selectedIds = get(this.props, 'Templates.selectedTemplateIds', []);
|
|
3774
3836
|
const selectedIdsArray = selectedIds.toJS ? selectedIds.toJS() : selectedIds;
|
|
3775
3837
|
temp.cardTop = (
|
|
3776
|
-
<
|
|
3838
|
+
<CapRow type="flex" align="middle" justify="space-between" className="template-card-top-bar">
|
|
3777
3839
|
<CapCheckbox
|
|
3778
3840
|
checked={selectedIdsArray.includes(template._id)}
|
|
3779
3841
|
onChange={() => this.props.actions.toggleTemplateSelection(template._id)}
|
|
3780
3842
|
onClick={(e) => e.stopPropagation()}
|
|
3781
3843
|
/>
|
|
3782
|
-
</
|
|
3844
|
+
</CapRow>
|
|
3783
3845
|
);
|
|
3784
3846
|
}
|
|
3785
3847
|
|
|
@@ -3791,46 +3853,39 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3791
3853
|
{template?.name}
|
|
3792
3854
|
{template.isArchived && <CapColoredTag tagColor={CAP_G08} tagTextColor={CAP_G05} className="archived-tag">{this.props.intl.formatMessage(messages.archivedTag)}</CapColoredTag>}
|
|
3793
3855
|
</span>
|
|
3794
|
-
{this.props.location.query.type !==
|
|
3856
|
+
{this.props.location.query.type !== EMBEDDED && <CapPopover
|
|
3795
3857
|
trigger="click"
|
|
3796
3858
|
content={
|
|
3797
3859
|
<div className="popover-content">
|
|
3798
3860
|
{this.state.channel !== 'wechat' && !template.isArchived && <div className="popover-action-container">
|
|
3799
|
-
<
|
|
3861
|
+
<CapButton type="link" onClick={() => this.duplicateTemplate(template)} className="popover-action">
|
|
3862
|
+
{this.props.intl.formatMessage(messages.duplicateButton)}
|
|
3863
|
+
</CapButton>
|
|
3800
3864
|
</div>}
|
|
3801
3865
|
{this.props.isFullMode && isTemplateArchiveEligible && !template.isArchived && <div className="popover-action-container">
|
|
3802
|
-
<
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
title: this.props.intl.formatMessage(messages.archiveTemplates),
|
|
3806
|
-
onConfirm: () => this.props.actions.archiveTemplate(this.state.channel, template._id, template.name),
|
|
3807
|
-
count: 1,
|
|
3808
|
-
});
|
|
3809
|
-
}}
|
|
3866
|
+
<CapButton
|
|
3867
|
+
type="link"
|
|
3868
|
+
onClick={() => this.handleTemplateArchiveAction({ templateId: template._id, templateName: template.name })}
|
|
3810
3869
|
className="popover-action popover-archive-action"
|
|
3811
3870
|
>
|
|
3812
|
-
<CapIcon type="archive" size="
|
|
3813
|
-
{this.props.intl.formatMessage(messages.archiveButton)}
|
|
3814
|
-
</
|
|
3871
|
+
<CapIcon type="archive" size="s" />
|
|
3872
|
+
<CapLabel.CapLabelInline type="label1">{this.props.intl.formatMessage(messages.archiveButton)}</CapLabel.CapLabelInline>
|
|
3873
|
+
</CapButton>
|
|
3815
3874
|
</div>}
|
|
3816
3875
|
{this.props.isFullMode && isTemplateArchiveEligible && template.isArchived && <div className="popover-action-container">
|
|
3817
|
-
<
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
title: this.props.intl.formatMessage(messages.unarchiveTemplates),
|
|
3821
|
-
onConfirm: () => this.props.actions.unarchiveTemplate(this.state.channel, template._id, template.name),
|
|
3822
|
-
count: 1,
|
|
3823
|
-
isUnarchive: true,
|
|
3824
|
-
});
|
|
3825
|
-
}}
|
|
3876
|
+
<CapButton
|
|
3877
|
+
type="link"
|
|
3878
|
+
onClick={() => this.handleTemplateArchiveAction({ templateId: template._id, templateName: template.name, isUnarchive: true })}
|
|
3826
3879
|
className="popover-action popover-archive-action"
|
|
3827
3880
|
>
|
|
3828
|
-
<CapIcon type="archive" size="
|
|
3829
|
-
{this.props.intl.formatMessage(messages.unarchiveButton)}
|
|
3830
|
-
</
|
|
3881
|
+
<CapIcon type="archive" size="s" />
|
|
3882
|
+
<CapLabel.CapLabelInline type="label1">{this.props.intl.formatMessage(messages.unarchiveButton)}</CapLabel.CapLabelInline>
|
|
3883
|
+
</CapButton>
|
|
3831
3884
|
</div>}
|
|
3832
3885
|
<div className="popover-action-container">
|
|
3833
|
-
<
|
|
3886
|
+
<CapButton type="link" onClick={() => this.toggleDeleteTemplateModal(template)} className="popover-action">
|
|
3887
|
+
{deleteOption}
|
|
3888
|
+
</CapButton>
|
|
3834
3889
|
</div>
|
|
3835
3890
|
</div>
|
|
3836
3891
|
}
|
|
@@ -3883,7 +3938,27 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
3883
3938
|
return false;
|
|
3884
3939
|
}
|
|
3885
3940
|
}
|
|
3886
|
-
isFullMode = () => this.props.location.query.type !==
|
|
3941
|
+
isFullMode = () => this.props.location.query.type !== EMBEDDED || this.props.isFullMode
|
|
3942
|
+
|
|
3943
|
+
isChannelArchiveEligible = (channel, whatsappStatus, rcsStatus) => (
|
|
3944
|
+
channel !== ZALO &&
|
|
3945
|
+
!(channel === WHATSAPP && [WHATSAPP_STATUSES.awaitingApproval, WHATSAPP_STATUSES.pending, WHATSAPP_STATUSES.unsubmitted].includes(whatsappStatus)) &&
|
|
3946
|
+
!(channel === RCS && [RCS_STATUSES.awaitingApproval, RCS_STATUSES.pending].includes(rcsStatus))
|
|
3947
|
+
)
|
|
3948
|
+
|
|
3949
|
+
enterArchivedMode = () => {
|
|
3950
|
+
this.props.actions.setArchivedMode(true);
|
|
3951
|
+
this.setState({ searchText: '', page: 1 }, () => {
|
|
3952
|
+
this.getAllTemplates({ params: { name: '', sortBy: this.state.sortBy, archiveStatus: 'archived' }, resetPage: true }, true);
|
|
3953
|
+
});
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3956
|
+
exitArchivedMode = () => {
|
|
3957
|
+
this.props.actions.setArchivedMode(false);
|
|
3958
|
+
this.setState({ searchText: '', page: 1 }, () => {
|
|
3959
|
+
this.getAllTemplates({ params: { name: '', sortBy: this.state.sortBy, archiveStatus: 'active' }, resetPage: true }, true);
|
|
3960
|
+
});
|
|
3961
|
+
}
|
|
3887
3962
|
isCreateDisabled = () => {
|
|
3888
3963
|
let isDisabled = this.isLoading();
|
|
3889
3964
|
const channel = this.state.channel.toUpperCase();
|
|
@@ -4594,20 +4669,14 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4594
4669
|
: isfilterContentVisisble && !isWechatEmbedded && !this.props.isDltFromRcs && createButton
|
|
4595
4670
|
)}
|
|
4596
4671
|
{/* More (⋯) menu: full mode only, not archived mode, not Zalo (no archive support), not when selection active */}
|
|
4597
|
-
{!_isArchivedMode && !_renderHasSelection && this.props.isFullMode && this.props.location.query.type !==
|
|
4672
|
+
{!_isArchivedMode && !_renderHasSelection && this.props.isFullMode && this.props.location.query.type !== EMBEDDED && channelLowerCase !== ZALO_LOWERCASE && (
|
|
4598
4673
|
<CapDropdown
|
|
4599
4674
|
trigger={['click']}
|
|
4600
4675
|
overlay={
|
|
4601
4676
|
<CapMenu>
|
|
4602
4677
|
<CapMenu.Item
|
|
4603
4678
|
key="archived"
|
|
4604
|
-
onClick={
|
|
4605
|
-
this.props.actions.setArchivedMode(true);
|
|
4606
|
-
this.setState({ searchText: '', page: 1 }, () => {
|
|
4607
|
-
const params = { name: '', sortBy: this.state.sortBy, archiveStatus: 'archived' };
|
|
4608
|
-
this.getAllTemplates({ params, resetPage: true }, true);
|
|
4609
|
-
});
|
|
4610
|
-
}}
|
|
4679
|
+
onClick={this.enterArchivedMode}
|
|
4611
4680
|
>
|
|
4612
4681
|
<FormattedMessage {...messages.archivedTemplates} />
|
|
4613
4682
|
</CapMenu.Item>
|
|
@@ -4658,6 +4727,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4658
4727
|
className={`creatives-templates-list ${
|
|
4659
4728
|
this.props.isFullMode ? "full-mode" : "library-mode"
|
|
4660
4729
|
}`}
|
|
4730
|
+
style={{ position: 'relative' }}
|
|
4661
4731
|
>
|
|
4662
4732
|
<input
|
|
4663
4733
|
type="file"
|
|
@@ -4675,13 +4745,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4675
4745
|
<CapIcon
|
|
4676
4746
|
type="back"
|
|
4677
4747
|
className="archived-mode-back-icon"
|
|
4678
|
-
onClick={
|
|
4679
|
-
this.props.actions.setArchivedMode(false);
|
|
4680
|
-
this.setState({ searchText: '', page: 1 }, () => {
|
|
4681
|
-
const params = { name: '', sortBy: this.state.sortBy, archiveStatus: 'active' };
|
|
4682
|
-
this.getAllTemplates({ params, resetPage: true }, true);
|
|
4683
|
-
});
|
|
4684
|
-
}}
|
|
4748
|
+
onClick={this.exitArchivedMode}
|
|
4685
4749
|
/>
|
|
4686
4750
|
<CapHeading type="h3"><FormattedMessage {...messages.archivedTemplates} /></CapHeading>
|
|
4687
4751
|
</CapRow>
|
|
@@ -686,4 +686,24 @@ export default defineMessages({
|
|
|
686
686
|
id: `${scope}.noArchivedCreativesDesc`,
|
|
687
687
|
defaultMessage: 'Creatives you archive will appear here',
|
|
688
688
|
},
|
|
689
|
+
"templateNameLabel": {
|
|
690
|
+
id: `${scope}.templateNameLabel`,
|
|
691
|
+
defaultMessage: 'Template name ',
|
|
692
|
+
},
|
|
693
|
+
"archiveTemplateSuccess": {
|
|
694
|
+
id: `${scope}.archiveTemplateSuccess`,
|
|
695
|
+
defaultMessage: 'Template archived successfully',
|
|
696
|
+
},
|
|
697
|
+
"unarchiveTemplateSuccess": {
|
|
698
|
+
id: `${scope}.unarchiveTemplateSuccess`,
|
|
699
|
+
defaultMessage: 'Template unarchived successfully',
|
|
700
|
+
},
|
|
701
|
+
"bulkArchiveSuccess": {
|
|
702
|
+
id: `${scope}.bulkArchiveSuccess`,
|
|
703
|
+
defaultMessage: '{count} {count, plural, one {template} other {templates}} archived successfully',
|
|
704
|
+
},
|
|
705
|
+
"bulkUnarchiveSuccess": {
|
|
706
|
+
id: `${scope}.bulkUnarchiveSuccess`,
|
|
707
|
+
defaultMessage: '{count} {count, plural, one {template} other {templates}} unarchived successfully',
|
|
708
|
+
},
|
|
689
709
|
});
|
|
@@ -33,6 +33,7 @@ export const initialState = fromJS({
|
|
|
33
33
|
unarchiveInProgress: false,
|
|
34
34
|
bulkArchiveInProgress: false,
|
|
35
35
|
bulkUnarchiveInProgress: false,
|
|
36
|
+
archiveListingRefreshType: null,
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
function templatesReducer(state = initialState, action) {
|
|
@@ -44,16 +45,18 @@ function templatesReducer(state = initialState, action) {
|
|
|
44
45
|
case types.GET_ALL_TEMPLATES_SUCCESS:
|
|
45
46
|
if (action.isReset) {
|
|
46
47
|
return state.set('getAllTemplatesInProgress', false)
|
|
48
|
+
.set('archiveListingRefreshType', null)
|
|
47
49
|
.set('templates', action.data ? action.data.templates : [])
|
|
48
50
|
.set('isSearch', action.data ? action.data.search : false)
|
|
49
51
|
.set('weCRMtemplates', action.weCRMTemplate ? action.weCRMTemplate : []).set('templateError', {});
|
|
50
52
|
}
|
|
51
53
|
return state.set('getAllTemplatesInProgress', false)
|
|
54
|
+
.set('archiveListingRefreshType', null)
|
|
52
55
|
.set('templates', action.data ? state.get('templates').concat(action.data.templates) : [])
|
|
53
56
|
.set('isSearch', action.data ? action.data.search : false)
|
|
54
57
|
.set('weCRMtemplates', action.weCRMTemplate ? state.get('weCRMtemplates').concat(action.weCRMTemplate) : []);
|
|
55
58
|
case types.GET_ALL_TEMPLATES_FAILURE:
|
|
56
|
-
return state.set('getAllTemplatesInProgress', false).set('templateError', action.error);
|
|
59
|
+
return state.set('getAllTemplatesInProgress', false).set('archiveListingRefreshType', null).set('templateError', action.error);
|
|
57
60
|
case types.DELETE_TEMPLATE_REQUEST:
|
|
58
61
|
return state.set('deleteTemplateInProgress', true)
|
|
59
62
|
.set('deleteTemplateError', null);
|
|
@@ -266,39 +269,51 @@ function templatesReducer(state = initialState, action) {
|
|
|
266
269
|
case types.CLEAR_TEMPLATE_SELECTION:
|
|
267
270
|
return state.set('selectedTemplateIds', fromJS([]));
|
|
268
271
|
case types.ARCHIVE_TEMPLATE_REQUEST:
|
|
269
|
-
return state.set('archiveInProgress', true);
|
|
272
|
+
return state.set('archiveInProgress', true).set('archiveError', null);
|
|
270
273
|
case types.ARCHIVE_TEMPLATE_SUCCESS: {
|
|
271
274
|
const afterArchive = state.get('selectedTemplateIds');
|
|
272
275
|
const archiveSelected = afterArchive && typeof afterArchive.toJS === 'function' ? afterArchive.toJS() : [];
|
|
273
276
|
return state
|
|
274
277
|
.set('archiveInProgress', false)
|
|
278
|
+
.set('archiveError', null)
|
|
279
|
+
.set('archiveSuccessPayload', { successMessage: action.successMessage, description: action.description })
|
|
275
280
|
.set('selectedTemplateIds', fromJS(archiveSelected.filter((sid) => sid !== action.id)));
|
|
276
281
|
}
|
|
277
282
|
case types.ARCHIVE_TEMPLATE_FAILURE:
|
|
278
|
-
return state.set('archiveInProgress', false);
|
|
283
|
+
return state.set('archiveInProgress', false).set('archiveError', action.error);
|
|
279
284
|
case types.UNARCHIVE_TEMPLATE_REQUEST:
|
|
280
|
-
return state.set('unarchiveInProgress', true);
|
|
285
|
+
return state.set('unarchiveInProgress', true).set('unarchiveError', null);
|
|
281
286
|
case types.UNARCHIVE_TEMPLATE_SUCCESS: {
|
|
282
287
|
const afterUnarchive = state.get('selectedTemplateIds');
|
|
283
288
|
const unarchiveSelected = afterUnarchive && typeof afterUnarchive.toJS === 'function' ? afterUnarchive.toJS() : [];
|
|
284
289
|
return state
|
|
285
290
|
.set('unarchiveInProgress', false)
|
|
291
|
+
.set('unarchiveError', null)
|
|
292
|
+
.set('unarchiveSuccessPayload', { successMessage: action.successMessage, description: action.description })
|
|
286
293
|
.set('selectedTemplateIds', fromJS(unarchiveSelected.filter((sid) => sid !== action.id)));
|
|
287
294
|
}
|
|
288
295
|
case types.UNARCHIVE_TEMPLATE_FAILURE:
|
|
289
|
-
return state.set('unarchiveInProgress', false);
|
|
296
|
+
return state.set('unarchiveInProgress', false).set('unarchiveError', action.error);
|
|
290
297
|
case types.BULK_ARCHIVE_REQUEST:
|
|
291
|
-
return state.set('bulkArchiveInProgress', true);
|
|
298
|
+
return state.set('bulkArchiveInProgress', true).set('bulkArchiveError', null);
|
|
292
299
|
case types.BULK_ARCHIVE_SUCCESS:
|
|
293
|
-
return state
|
|
300
|
+
return state
|
|
301
|
+
.set('bulkArchiveInProgress', false)
|
|
302
|
+
.set('bulkArchiveError', null)
|
|
303
|
+
.set('bulkArchiveSuccessPayload', { successMessage: action.successMessage, count: action.count })
|
|
304
|
+
.set('selectedTemplateIds', fromJS([]));
|
|
294
305
|
case types.BULK_ARCHIVE_FAILURE:
|
|
295
|
-
return state.set('bulkArchiveInProgress', false);
|
|
306
|
+
return state.set('bulkArchiveInProgress', false).set('bulkArchiveError', action.error);
|
|
296
307
|
case types.BULK_UNARCHIVE_REQUEST:
|
|
297
|
-
return state.set('bulkUnarchiveInProgress', true);
|
|
308
|
+
return state.set('bulkUnarchiveInProgress', true).set('bulkUnarchiveError', null);
|
|
298
309
|
case types.BULK_UNARCHIVE_SUCCESS:
|
|
299
|
-
return state
|
|
310
|
+
return state
|
|
311
|
+
.set('bulkUnarchiveInProgress', false)
|
|
312
|
+
.set('bulkUnarchiveError', null)
|
|
313
|
+
.set('bulkUnarchiveSuccessPayload', { successMessage: action.successMessage, count: action.count })
|
|
314
|
+
.set('selectedTemplateIds', fromJS([]));
|
|
300
315
|
case types.BULK_UNARCHIVE_FAILURE:
|
|
301
|
-
return state.set('bulkUnarchiveInProgress', false);
|
|
316
|
+
return state.set('bulkUnarchiveInProgress', false).set('bulkUnarchiveError', action.error);
|
|
302
317
|
default:
|
|
303
318
|
return state;
|
|
304
319
|
}
|