@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.
@@ -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 !== ZALO &&
1909
- !(currentChannel === WHATSAPP && [WHATSAPP_STATUSES.awaitingApproval, WHATSAPP_STATUSES.pending, WHATSAPP_STATUSES.unsubmitted].includes(cardWhatsappStatus)) &&
1910
- !(currentChannel === RCS && [RCS_STATUSES.awaitingApproval, RCS_STATUSES.pending].includes(cardRcsStatus));
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 title={template?.name} style={{ display: 'flex', alignItems: 'center' }}>
1915
- {this.props.isFullMode && this.props.location.query.type !== 'embedded' && isCardArchiveEligible && (
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
- style={{ marginRight: CAP_SPACE_08, flexShrink: 0 }}
1955
+ disabled={isAnyArchiveInProgress}
1921
1956
  />
1922
1957
  )}
1923
- {template?.name}
1924
- {currentChannel === INAPP && (
1925
- <CapRow>
1926
- <CapColoredTag
1927
- tagColor={INAPP_LAYOUT_DETAILS[inappBodyType]?.tagColor}
1928
- tagTextColor={
1929
- INAPP_LAYOUT_DETAILS[inappBodyType]?.tagTextColor
1930
- }
1931
- tagHeight="1.25rem"
1932
- tagFontSize="12px"
1933
- >
1934
- {INAPP_LAYOUT_DETAILS[inappBodyType]?.text}
1935
- </CapColoredTag>
1936
- </CapRow>
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
- const isArchiveEligible = channelUp !== ZALO &&
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 !== 'embedded' && isCardArchiveEligible && (
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
- style={{ marginRight: CAP_SPACE_08, flexShrink: 0 }}
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 !== 'embedded' && isCardArchiveEligible && (
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
- style={{ marginRight: CAP_SPACE_08, flexShrink: 0 }}
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
- <div className={this.isFullMode() ? 'illustration-scroll-wrapper' : ''}>
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
- </div>
2767
+ </CapRow>
2755
2768
  )}
2756
2769
  {<CapCustomSkeleton loader={isInitialLoading && (isLoading || getAllTemplatesInProgress)} />}
2757
- {<CapPageSpinner spinning={!isInitialLoading && (isLoading || getAllTemplatesInProgress)} />}
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 !== ZALO &&
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 !== 'embedded' && isTemplateArchiveEligible) {
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
- <div className="template-card-top-bar">
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
- </div>
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 !== 'embedded' && <CapPopover
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
- <span onClick={() => this.duplicateTemplate(template)} className="popover-action" style={{cursor: 'pointer', padding: '8px 0px'}}>{this.props.intl.formatMessage(messages.duplicateButton)}</span>
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
- <span
3803
- onClick={() => {
3804
- this.showArchiveConfirm({
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="l" />
3813
- {this.props.intl.formatMessage(messages.archiveButton)}
3814
- </span>
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
- <span
3818
- onClick={() => {
3819
- this.showArchiveConfirm({
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="l" />
3829
- {this.props.intl.formatMessage(messages.unarchiveButton)}
3830
- </span>
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
- <span onClick={() => this.toggleDeleteTemplateModal(template)} className="popover-action" style={{cursor: 'pointer', padding: '8px 0px'}}>{deleteOption}</span>
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 !== "embedded" || this.props.isFullMode
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 !== 'embedded' && channelLowerCase !== ZALO_LOWERCASE && (
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.set('bulkArchiveInProgress', false).set('selectedTemplateIds', fromJS([]));
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.set('bulkUnarchiveInProgress', false).set('selectedTemplateIds', fromJS([]));
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
  }