@capillarytech/creatives-library 9.0.70 → 9.0.71-alpha.sprint-cap-211543.18
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/constants/unified.js +0 -1
- package/package.json +1 -1
- package/utils/common.js +0 -6
- package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +58 -1
- package/v2Components/CapTagListWithInput/index.js +64 -35
- package/v2Components/CapTagListWithInput/index.scss +9 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/EmailPreviewContent.js +8 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +7 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +1 -0
- package/v2Components/CommonTestAndPreview/actions.js +1 -1
- package/v2Components/CommonTestAndPreview/index.js +29 -13
- package/v2Components/CommonTestAndPreview/sagas.js +2 -1
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/EmailPreviewContent.test.js +27 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +40 -0
- package/v2Components/CommonTestAndPreview/tests/index.test.js +95 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +24 -0
- package/v2Components/EmailMobilePreview/index.js +8 -1
- package/v2Components/EmailMobilePreview/index.scss +12 -3
- package/v2Components/EmailMobilePreview/tests/index.test.js +31 -0
- package/v2Components/EmailPreviewV2/_emailPreviewV2.scss +35 -5
- package/v2Components/EmailPreviewV2/index.js +18 -6
- package/v2Components/EmailPreviewV2/messages.js +4 -0
- package/v2Components/EmailPreviewV2/tests/index.test.js +43 -0
- package/v2Components/FormBuilder/Classic.js +119 -10
- package/v2Components/FormBuilder/_formBuilder.scss +21 -0
- package/v2Components/FormBuilder/messages.js +12 -0
- package/v2Containers/Cap/tests/index.test.js +1 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +2 -1
- package/v2Containers/CommunicationFlow/Tests/constants.test.js +4 -4
- package/v2Containers/CommunicationFlow/constants.js +1 -0
- package/v2Containers/CommunicationFlow/utils/Tests/mapCommDefinitionToStepData.test.js +2 -1
- package/v2Containers/CommunicationFlow/utils/mapCommDefinitionToStepData.js +1 -0
- package/v2Containers/CreativesContainer/index.js +9 -3
- package/v2Containers/CreativesContainer/tests/index.test.js +81 -0
- package/v2Containers/Email/index.js +31 -4
- package/v2Containers/Email/initialSchema.js +30 -0
- package/v2Containers/Email/messages.js +8 -0
- package/v2Containers/Email/tests/index.test.js +107 -0
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +172 -8
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +264 -5
- package/v2Containers/EmailWrapper/components/_emailHTMLEditor.scss +29 -0
- package/v2Containers/EmailWrapper/constants.js +5 -0
- package/v2Containers/EmailWrapper/messages.js +24 -0
- package/v2Containers/TagList/index.js +7 -0
- package/v2Containers/TagList/tests/TagList.test.js +42 -0
- package/v2Containers/Templates/constants.js +1 -0
- package/v2Containers/Templates/index.js +31 -27
- package/v2Containers/Templates/messages.js +4 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +128 -4
- package/v2Containers/Templates/tests/archival.test.js +221 -0
|
@@ -12,7 +12,7 @@ import '@testing-library/jest-dom';
|
|
|
12
12
|
import { IntlProvider } from 'react-intl';
|
|
13
13
|
import EmailHTMLEditor from '../EmailHTMLEditor';
|
|
14
14
|
import { validateLiquidTemplateContent } from '../../../../utils/commonUtils';
|
|
15
|
-
import { validateTags } from '../../../../utils/tagValidations';
|
|
15
|
+
import { validateTags, hasUnsubscribeTag } from '../../../../utils/tagValidations';
|
|
16
16
|
import { isEmailUnsubscribeTagOptional } from '../../../../utils/common';
|
|
17
17
|
import { downloadHtmlFile } from '../../../../utils/downloadFile';
|
|
18
18
|
|
|
@@ -23,6 +23,7 @@ jest.mock('../../../../utils/commonUtils', () => ({
|
|
|
23
23
|
|
|
24
24
|
jest.mock('../../../../utils/tagValidations', () => ({
|
|
25
25
|
validateTags: jest.fn(),
|
|
26
|
+
hasUnsubscribeTag: jest.fn(() => false),
|
|
26
27
|
}));
|
|
27
28
|
|
|
28
29
|
jest.mock('../../../../utils/common', () => ({
|
|
@@ -217,11 +218,12 @@ jest.mock('../../../../v2Components/CapTagListWithInput', () => {
|
|
|
217
218
|
React.useEffect(() => {
|
|
218
219
|
setValue(props.inputValue || '');
|
|
219
220
|
}, [props.inputValue]);
|
|
221
|
+
const isPreheader = props.inputId === 'template-preheader';
|
|
220
222
|
return (
|
|
221
223
|
<div data-testid="cap-tag-list-input">
|
|
222
224
|
<input
|
|
223
|
-
id=
|
|
224
|
-
data-testid=
|
|
225
|
+
id={props.inputId || 'template-subject'}
|
|
226
|
+
data-testid={isPreheader ? 'preheader-input' : 'subject-input'}
|
|
225
227
|
value={value}
|
|
226
228
|
onChange={(e) => {
|
|
227
229
|
setValue(e.target.value);
|
|
@@ -233,16 +235,35 @@ jest.mock('../../../../v2Components/CapTagListWithInput', () => {
|
|
|
233
235
|
/>
|
|
234
236
|
<button
|
|
235
237
|
onClick={() => props.onTagSelect && props.onTagSelect('customer.name')}
|
|
236
|
-
data-testid=
|
|
238
|
+
data-testid={isPreheader ? 'preheader-trigger-tag-select' : 'trigger-tag-select'}
|
|
237
239
|
>
|
|
238
240
|
Select Tag
|
|
239
241
|
</button>
|
|
242
|
+
{isPreheader && (
|
|
243
|
+
<button
|
|
244
|
+
onClick={() => props.onTagSelect && props.onTagSelect('')}
|
|
245
|
+
data-testid="preheader-trigger-tag-select-empty"
|
|
246
|
+
>
|
|
247
|
+
Select Empty Tag
|
|
248
|
+
</button>
|
|
249
|
+
)}
|
|
240
250
|
<button
|
|
241
251
|
onClick={() => props.onContextChange && props.onContextChange('default')}
|
|
242
|
-
data-testid=
|
|
252
|
+
data-testid={isPreheader ? 'preheader-trigger-context-change' : 'trigger-context-change'}
|
|
243
253
|
>
|
|
244
254
|
Change Context
|
|
245
255
|
</button>
|
|
256
|
+
{props.extraContent}
|
|
257
|
+
{props.inputProps && props.inputProps.prefix}
|
|
258
|
+
{props.inputProps && props.inputProps.suffix}
|
|
259
|
+
<span data-testid={isPreheader ? 'preheader-exclude-tags' : 'subject-exclude-tags'}>
|
|
260
|
+
{JSON.stringify(props.excludeTags || [])}
|
|
261
|
+
</span>
|
|
262
|
+
{props.inputErrorMessage && (
|
|
263
|
+
<span data-testid={isPreheader ? 'preheader-error' : 'subject-error'}>
|
|
264
|
+
{props.inputErrorMessage}
|
|
265
|
+
</span>
|
|
266
|
+
)}
|
|
246
267
|
</div>
|
|
247
268
|
);
|
|
248
269
|
};
|
|
@@ -407,6 +428,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
407
428
|
jest.clearAllMocks();
|
|
408
429
|
validateLiquidTemplateContent.mockResolvedValue(true);
|
|
409
430
|
validateTags.mockReturnValue({ valid: true });
|
|
431
|
+
hasUnsubscribeTag.mockReturnValue(false);
|
|
410
432
|
isEmailUnsubscribeTagOptional.mockReturnValue(false);
|
|
411
433
|
// Reset mock functions
|
|
412
434
|
mockGetAllIssues.mockReturnValue([]);
|
|
@@ -754,6 +776,186 @@ describe('EmailHTMLEditor', () => {
|
|
|
754
776
|
});
|
|
755
777
|
});
|
|
756
778
|
|
|
779
|
+
describe('Pre-header Handling', () => {
|
|
780
|
+
const revealPreheader = () => {
|
|
781
|
+
fireEvent.click(screen.getByText('+ Pre-header'));
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
it('uses the updated placeholder text and 85-character limit', () => {
|
|
785
|
+
renderWithIntl();
|
|
786
|
+
revealPreheader();
|
|
787
|
+
|
|
788
|
+
expect(screen.getByTestId('preheader-input').placeholder).toBe('e.g. Get 20% off your next order');
|
|
789
|
+
expect(screen.getByText('0 / 85')).toBeInTheDocument();
|
|
790
|
+
|
|
791
|
+
fireEvent.change(screen.getByTestId('preheader-input'), { target: { value: 'Hello' } });
|
|
792
|
+
expect(screen.getByText('5 / 85')).toBeInTheDocument();
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it('shows an info tooltip icon next to the Pre-header label', () => {
|
|
796
|
+
const { container } = renderWithIntl();
|
|
797
|
+
revealPreheader();
|
|
798
|
+
|
|
799
|
+
expect(container.querySelector('.cap-icon-v2-info-circle')).toBeInTheDocument();
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
it('inserts tag into Pre-header field at the cursor position', () => {
|
|
803
|
+
renderWithIntl();
|
|
804
|
+
revealPreheader();
|
|
805
|
+
const input = screen.getByTestId('preheader-input');
|
|
806
|
+
fireEvent.change(input, { target: { value: 'Hello ' } });
|
|
807
|
+
Object.defineProperty(input, 'selectionStart', { value: 6, writable: true });
|
|
808
|
+
Object.defineProperty(input, 'selectionEnd', { value: 6, writable: true });
|
|
809
|
+
|
|
810
|
+
fireEvent.click(screen.getByTestId('preheader-trigger-tag-select'));
|
|
811
|
+
|
|
812
|
+
expect(screen.getByTestId('preheader-input').value).toBe('Hello {{customer.name}}');
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it('appends the tag when the Pre-header input has no selection', () => {
|
|
816
|
+
renderWithIntl();
|
|
817
|
+
revealPreheader();
|
|
818
|
+
const input = screen.getByTestId('preheader-input');
|
|
819
|
+
fireEvent.change(input, { target: { value: 'Hello' } });
|
|
820
|
+
Object.defineProperty(input, 'selectionStart', { value: undefined, writable: true });
|
|
821
|
+
Object.defineProperty(input, 'selectionEnd', { value: undefined, writable: true });
|
|
822
|
+
|
|
823
|
+
fireEvent.click(screen.getByTestId('preheader-trigger-tag-select'));
|
|
824
|
+
|
|
825
|
+
expect(screen.getByTestId('preheader-input').value).toBe('Hello{{customer.name}}');
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
it('appends the tag when the Pre-header input element is not found', () => {
|
|
829
|
+
renderWithIntl();
|
|
830
|
+
revealPreheader();
|
|
831
|
+
const input = screen.getByTestId('preheader-input');
|
|
832
|
+
fireEvent.change(input, { target: { value: 'Hello' } });
|
|
833
|
+
input.remove();
|
|
834
|
+
|
|
835
|
+
fireEvent.click(screen.getByTestId('preheader-trigger-tag-select'));
|
|
836
|
+
// Should handle gracefully without throwing
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
it('falls back to a safe append when reading the Pre-header input throws', () => {
|
|
840
|
+
renderWithIntl();
|
|
841
|
+
revealPreheader();
|
|
842
|
+
const input = screen.getByTestId('preheader-input');
|
|
843
|
+
fireEvent.change(input, { target: { value: 'Hello' } });
|
|
844
|
+
Object.defineProperty(input, 'selectionStart', {
|
|
845
|
+
configurable: true,
|
|
846
|
+
get() { throw new Error('boom'); },
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
expect(() => fireEvent.click(screen.getByTestId('preheader-trigger-tag-select'))).not.toThrow();
|
|
850
|
+
expect(screen.getByTestId('preheader-input').value).toBe('Hello{{customer.name}}');
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
it('does nothing when the Pre-header tag select fires without data', () => {
|
|
854
|
+
renderWithIntl();
|
|
855
|
+
revealPreheader();
|
|
856
|
+
fireEvent.change(screen.getByTestId('preheader-input'), { target: { value: 'Hello' } });
|
|
857
|
+
|
|
858
|
+
fireEvent.click(screen.getByTestId('preheader-trigger-tag-select-empty'));
|
|
859
|
+
|
|
860
|
+
expect(screen.getByTestId('preheader-input').value).toBe('Hello');
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
it('inserts a tag into an empty Pre-header field', () => {
|
|
864
|
+
renderWithIntl();
|
|
865
|
+
revealPreheader();
|
|
866
|
+
|
|
867
|
+
fireEvent.click(screen.getByTestId('preheader-trigger-tag-select'));
|
|
868
|
+
|
|
869
|
+
expect(screen.getByTestId('preheader-input').value).toBe('{{customer.name}}');
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
it('handles the Pre-header context-change trigger', () => {
|
|
873
|
+
renderWithIntl();
|
|
874
|
+
revealPreheader();
|
|
875
|
+
|
|
876
|
+
fireEvent.click(screen.getByTestId('preheader-trigger-context-change'));
|
|
877
|
+
// Exercises handleOnTagsContextChange for the Pre-header field
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
it('clears a stale Pre-header error once the user edits the field again', () => {
|
|
881
|
+
hasUnsubscribeTag.mockReturnValue(true);
|
|
882
|
+
const { rerender } = renderWithIntl({ isGetFormData: false });
|
|
883
|
+
fireEvent.change(screen.getByTestId('subject-input'), { target: { value: 'Valid Subject' } });
|
|
884
|
+
revealPreheader();
|
|
885
|
+
fireEvent.change(screen.getByTestId('preheader-input'), { target: { value: '{{unsubscribe}}' } });
|
|
886
|
+
|
|
887
|
+
rerender(
|
|
888
|
+
<IntlProvider locale="en" messages={{}}>
|
|
889
|
+
<EmailHTMLEditor {...defaultProps} isGetFormData />
|
|
890
|
+
</IntlProvider>
|
|
891
|
+
);
|
|
892
|
+
expect(screen.getByTestId('preheader-error')).toBeInTheDocument();
|
|
893
|
+
|
|
894
|
+
fireEvent.change(screen.getByTestId('preheader-input'), { target: { value: '{{unsubscribe}} extra' } });
|
|
895
|
+
|
|
896
|
+
expect(screen.queryByTestId('preheader-error')).not.toBeInTheDocument();
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
it('blocks save for an invalid Pre-header even when onValidationFail is not provided', () => {
|
|
900
|
+
hasUnsubscribeTag.mockReturnValue(true);
|
|
901
|
+
const emailActions = {
|
|
902
|
+
...defaultProps.emailActions,
|
|
903
|
+
createTemplate: jest.fn((obj, callback) => callback({ templateId: { _id: '123', versions: {} } })),
|
|
904
|
+
};
|
|
905
|
+
const props = { ...defaultProps, emailActions };
|
|
906
|
+
delete props.onValidationFail;
|
|
907
|
+
const { rerender } = render(
|
|
908
|
+
<IntlProvider locale="en" messages={{}}>
|
|
909
|
+
<EmailHTMLEditor {...props} isGetFormData={false} />
|
|
910
|
+
</IntlProvider>
|
|
911
|
+
);
|
|
912
|
+
fireEvent.change(screen.getByTestId('subject-input'), { target: { value: 'Valid Subject' } });
|
|
913
|
+
revealPreheader();
|
|
914
|
+
fireEvent.change(screen.getByTestId('preheader-input'), { target: { value: '{{unsubscribe}}' } });
|
|
915
|
+
|
|
916
|
+
expect(() => rerender(
|
|
917
|
+
<IntlProvider locale="en" messages={{}}>
|
|
918
|
+
<EmailHTMLEditor {...props} isGetFormData />
|
|
919
|
+
</IntlProvider>
|
|
920
|
+
)).not.toThrow();
|
|
921
|
+
|
|
922
|
+
expect(screen.getByTestId('preheader-error')).toBeInTheDocument();
|
|
923
|
+
expect(emailActions.createTemplate).not.toHaveBeenCalled();
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
it('removes the Pre-header field, clearing its value and error, when Remove is clicked', () => {
|
|
927
|
+
hasUnsubscribeTag.mockReturnValue(true);
|
|
928
|
+
const { rerender } = renderWithIntl({ isGetFormData: false });
|
|
929
|
+
fireEvent.change(screen.getByTestId('subject-input'), { target: { value: 'Valid Subject' } });
|
|
930
|
+
revealPreheader();
|
|
931
|
+
fireEvent.change(screen.getByTestId('preheader-input'), { target: { value: '{{unsubscribe}}' } });
|
|
932
|
+
rerender(
|
|
933
|
+
<IntlProvider locale="en" messages={{}}>
|
|
934
|
+
<EmailHTMLEditor {...defaultProps} isGetFormData />
|
|
935
|
+
</IntlProvider>
|
|
936
|
+
);
|
|
937
|
+
expect(screen.getByTestId('preheader-error')).toBeInTheDocument();
|
|
938
|
+
|
|
939
|
+
fireEvent.click(screen.getByText('Remove'));
|
|
940
|
+
|
|
941
|
+
expect(screen.queryByTestId('preheader-input')).not.toBeInTheDocument();
|
|
942
|
+
expect(screen.queryByTestId('preheader-error')).not.toBeInTheDocument();
|
|
943
|
+
expect(screen.getByText('+ Pre-header')).toBeInTheDocument();
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
it('defaults injectedTags to {} for the Pre-header tag list when the prop is falsy', () => {
|
|
947
|
+
// null (not undefined) is required here: a static defaultProps entry only kicks in for
|
|
948
|
+
// undefined, so deleting the prop would never actually exercise the `|| {}` fallback.
|
|
949
|
+
expect(() => render(
|
|
950
|
+
<IntlProvider locale="en" messages={{}}>
|
|
951
|
+
<EmailHTMLEditor {...defaultProps} injectedTags={null} />
|
|
952
|
+
</IntlProvider>
|
|
953
|
+
)).not.toThrow();
|
|
954
|
+
fireEvent.click(screen.getByText('+ Pre-header'));
|
|
955
|
+
expect(screen.getByTestId('preheader-input')).toBeInTheDocument();
|
|
956
|
+
});
|
|
957
|
+
});
|
|
958
|
+
|
|
757
959
|
describe('Content Change Handling', () => {
|
|
758
960
|
it('updates content when HTMLEditor changes', () => {
|
|
759
961
|
renderWithIntl();
|
|
@@ -899,6 +1101,63 @@ describe('EmailHTMLEditor', () => {
|
|
|
899
1101
|
}, { timeout: 3000 });
|
|
900
1102
|
});
|
|
901
1103
|
|
|
1104
|
+
it('hides the unsubscribe tag from the Pre-header Add Label dropdown but not Subject\'s', () => {
|
|
1105
|
+
renderWithIntl();
|
|
1106
|
+
fireEvent.click(screen.getByText('+ Pre-header'));
|
|
1107
|
+
expect(screen.getByTestId('preheader-exclude-tags')).toHaveTextContent(JSON.stringify(['unsubscribe']));
|
|
1108
|
+
expect(screen.getByTestId('subject-exclude-tags')).toHaveTextContent(JSON.stringify([]));
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
it('blocks save and shows an inline error when Pre-header contains the unsubscribe tag', async () => {
|
|
1112
|
+
const onValidationFail = jest.fn();
|
|
1113
|
+
hasUnsubscribeTag.mockReturnValue(true);
|
|
1114
|
+
const { rerender } = renderWithIntl({ onValidationFail, isGetFormData: false });
|
|
1115
|
+
fireEvent.change(screen.getByTestId('subject-input'), { target: { value: 'Valid Subject' } });
|
|
1116
|
+
fireEvent.click(screen.getByText('+ Pre-header'));
|
|
1117
|
+
fireEvent.change(screen.getByTestId('preheader-input'), { target: { value: '{{unsubscribe}}' } });
|
|
1118
|
+
|
|
1119
|
+
rerender(
|
|
1120
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1121
|
+
<EmailHTMLEditor {...defaultProps} onValidationFail={onValidationFail} isGetFormData />
|
|
1122
|
+
</IntlProvider>
|
|
1123
|
+
);
|
|
1124
|
+
|
|
1125
|
+
await waitFor(() => {
|
|
1126
|
+
expect(onValidationFail).toHaveBeenCalled();
|
|
1127
|
+
}, { timeout: 3000 });
|
|
1128
|
+
expect(defaultProps.emailActions.createTemplate).not.toHaveBeenCalled();
|
|
1129
|
+
expect(screen.getByTestId('preheader-error')).toHaveTextContent('Unsubscribe tag is not allowed in Pre-header.');
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
it('allows save when Pre-header does not contain the unsubscribe tag', async () => {
|
|
1133
|
+
hasUnsubscribeTag.mockReturnValue(false);
|
|
1134
|
+
const emailActions = {
|
|
1135
|
+
...defaultProps.emailActions,
|
|
1136
|
+
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
1137
|
+
createTemplate: jest.fn((obj, callback) => {
|
|
1138
|
+
callback({ templateId: { _id: '123', versions: {} } });
|
|
1139
|
+
}),
|
|
1140
|
+
};
|
|
1141
|
+
const { rerender } = renderWithIntl({
|
|
1142
|
+
isGetFormData: false, templateName: 'New Template', emailActions, getLiquidTags: null,
|
|
1143
|
+
});
|
|
1144
|
+
fireEvent.change(screen.getByTestId('subject-input'), { target: { value: 'Valid Subject' } });
|
|
1145
|
+
fireEvent.click(screen.getByText('+ Pre-header'));
|
|
1146
|
+
fireEvent.change(screen.getByTestId('preheader-input'), { target: { value: 'A normal preheader' } });
|
|
1147
|
+
fireEvent.click(screen.getByTestId('trigger-content-change'));
|
|
1148
|
+
|
|
1149
|
+
rerender(
|
|
1150
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1151
|
+
<EmailHTMLEditor {...defaultProps} isGetFormData templateName="New Template" emailActions={emailActions} getLiquidTags={null} />
|
|
1152
|
+
</IntlProvider>
|
|
1153
|
+
);
|
|
1154
|
+
|
|
1155
|
+
await waitFor(() => {
|
|
1156
|
+
expect(emailActions.createTemplate).toHaveBeenCalled();
|
|
1157
|
+
}, { timeout: 3000 });
|
|
1158
|
+
expect(screen.queryByTestId('preheader-error')).not.toBeInTheDocument();
|
|
1159
|
+
});
|
|
1160
|
+
|
|
902
1161
|
it('blocks save when there are HTML validation errors', async () => {
|
|
903
1162
|
const onValidationFail = jest.fn();
|
|
904
1163
|
// Mock getAllIssues to return BLOCKING errors (sanitizer errors or client-side Liquid errors)
|
|
@@ -9,3 +9,32 @@
|
|
|
9
9
|
.html-editor-options-icon {
|
|
10
10
|
cursor: pointer;
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
// Subject/Pre-header field rows
|
|
14
|
+
.email-html-editor-field {
|
|
15
|
+
margin-bottom: $CAP_SPACE_16;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.email-html-editor-add-preheader-btn {
|
|
19
|
+
white-space: nowrap;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Subject/Pre-header inline prefix label and Pre-header suffix (counter + Remove)
|
|
23
|
+
.email-html-editor-prefix {
|
|
24
|
+
padding-right: $CAP_SPACE_08;
|
|
25
|
+
margin-right: $CAP_SPACE_08;
|
|
26
|
+
border-right: 0.0625rem solid $CAP_COLOR_16;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.email-html-editor-prefix-label {
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.email-html-editor-counter {
|
|
34
|
+
white-space: nowrap;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.email-html-editor-remove-link {
|
|
38
|
+
white-space: nowrap;
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
}
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { UNSUBSCRIBE_TAG } from '../../constants/unified';
|
|
8
|
+
|
|
7
9
|
export const DEFAULT_ACTION = "app/EmailWrapper/DEFAULT_ACTION";
|
|
8
10
|
export const EMAIL_CREATE_MODES = {
|
|
9
11
|
UPLOAD: "upload",
|
|
@@ -17,3 +19,6 @@ export const STEPS = {
|
|
|
17
19
|
TEMPLATE_SELECTION: "templateSelection",
|
|
18
20
|
CREATE_TEMPLATE_CONTENT: "createTemplateContent",
|
|
19
21
|
};
|
|
22
|
+
|
|
23
|
+
// Unsubscribe is not a valid Pre-header tag — hide it from the "Add Label" dropdown there.
|
|
24
|
+
export const PREHEADER_EXCLUDED_TAGS = [UNSUBSCRIBE_TAG];
|
|
@@ -122,6 +122,30 @@ export default defineMessages({
|
|
|
122
122
|
id: 'creatives.containersV2.EmailWrapper.enterEmailSubject',
|
|
123
123
|
defaultMessage: 'Enter Email Subject',
|
|
124
124
|
},
|
|
125
|
+
preheader: {
|
|
126
|
+
id: 'creatives.containersV2.EmailWrapper.preheader',
|
|
127
|
+
defaultMessage: 'Pre-header',
|
|
128
|
+
},
|
|
129
|
+
enterEmailPreheader: {
|
|
130
|
+
id: 'creatives.containersV2.EmailWrapper.enterEmailPreheader',
|
|
131
|
+
defaultMessage: 'e.g. Get 20% off your next order',
|
|
132
|
+
},
|
|
133
|
+
preheaderInfoTooltip: {
|
|
134
|
+
id: 'creatives.containersV2.EmailWrapper.preheaderInfoTooltip',
|
|
135
|
+
defaultMessage: 'Shows next to the subject line in the inbox, use it to add context and improve open rates. Keep it to 40–85 characters.',
|
|
136
|
+
},
|
|
137
|
+
addPreheader: {
|
|
138
|
+
id: 'creatives.containersV2.EmailWrapper.addPreheader',
|
|
139
|
+
defaultMessage: '+ Pre-header',
|
|
140
|
+
},
|
|
141
|
+
removePreheader: {
|
|
142
|
+
id: 'creatives.containersV2.EmailWrapper.removePreheader',
|
|
143
|
+
defaultMessage: 'Remove',
|
|
144
|
+
},
|
|
145
|
+
unsubscribeNotAllowedInPreheader: {
|
|
146
|
+
id: 'creatives.containersV2.EmailWrapper.unsubscribeNotAllowedInPreheader',
|
|
147
|
+
defaultMessage: 'Unsubscribe tag is not allowed in Pre-header.',
|
|
148
|
+
},
|
|
125
149
|
editorType: {
|
|
126
150
|
id: 'creatives.containersV2.EmailWrapper.editorType',
|
|
127
151
|
defaultMessage: 'Editor type',
|
|
@@ -287,6 +287,10 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
287
287
|
//filtering BADGES_RELATED_TAGS if org does not have BADGES feature enabled
|
|
288
288
|
excludedTags.push(...BADGES_RELATED_TAGS);
|
|
289
289
|
}
|
|
290
|
+
if (this.props.excludeTags?.length) {
|
|
291
|
+
// Caller-specified exclusions (e.g. unsubscribe tag not allowed in the Pre-header field)
|
|
292
|
+
excludedTags.push(...this.props.excludeTags);
|
|
293
|
+
}
|
|
290
294
|
//Form tags object with tag headers
|
|
291
295
|
_.forEach(tagsList, (temp) => {
|
|
292
296
|
const tag = temp.definition;
|
|
@@ -491,12 +495,15 @@ TagList.defaultProps = {
|
|
|
491
495
|
userLocale: 'en',
|
|
492
496
|
eventContextTags: [],
|
|
493
497
|
waitEventContextTags: {},
|
|
498
|
+
excludeTags: [],
|
|
494
499
|
};
|
|
495
500
|
|
|
496
501
|
TagList.propTypes = {
|
|
497
502
|
onTagSelect: PropTypes.func,
|
|
498
503
|
tags: PropTypes.array,
|
|
499
504
|
injectedTags: PropTypes.object,
|
|
505
|
+
// Tag values (e.g. 'unsubscribe') to hide from the Add Label dropdown for this instance
|
|
506
|
+
excludeTags: PropTypes.array,
|
|
500
507
|
label: PropTypes.string,
|
|
501
508
|
onContextChange: PropTypes.func,
|
|
502
509
|
moduleFilterEnabled: PropTypes.bool,
|
|
@@ -105,6 +105,48 @@ describe("TagList test : UNIT", () => {
|
|
|
105
105
|
expect(btn).toBeDisabled();
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
+
it('hides a tag named in excludeTags from the Add Label dropdown, keeping other tags visible', () => {
|
|
109
|
+
const unsubscribeTag = {
|
|
110
|
+
_id: 'unsubscribe-tag-id',
|
|
111
|
+
type: 'TAG',
|
|
112
|
+
definition: {
|
|
113
|
+
label: { en: 'Unsubscribe' },
|
|
114
|
+
value: 'unsubscribe',
|
|
115
|
+
subtags: [],
|
|
116
|
+
'tag-header': false,
|
|
117
|
+
supportedModules: [{ context: 'default', layout: 'email', mandatory: false }],
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
initializeTagList({
|
|
121
|
+
tags: [...TagListData.tags, unsubscribeTag],
|
|
122
|
+
excludeTags: ['unsubscribe'],
|
|
123
|
+
moduleFilterEnabled: false,
|
|
124
|
+
});
|
|
125
|
+
addLabelBtnAssertion();
|
|
126
|
+
expect(queryByText(/Unsubscribe/i)).not.toBeInTheDocument();
|
|
127
|
+
expect(getByText(/Optout/i)).toBeInTheDocument();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('does not filter any tag when excludeTags is not provided (default behavior unaffected)', () => {
|
|
131
|
+
const unsubscribeTag = {
|
|
132
|
+
_id: 'unsubscribe-tag-id',
|
|
133
|
+
type: 'TAG',
|
|
134
|
+
definition: {
|
|
135
|
+
label: { en: 'Unsubscribe' },
|
|
136
|
+
value: 'unsubscribe',
|
|
137
|
+
subtags: [],
|
|
138
|
+
'tag-header': false,
|
|
139
|
+
supportedModules: [{ context: 'default', layout: 'email', mandatory: false }],
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
initializeTagList({
|
|
143
|
+
tags: [...TagListData.tags, unsubscribeTag],
|
|
144
|
+
moduleFilterEnabled: false,
|
|
145
|
+
});
|
|
146
|
+
addLabelBtnAssertion();
|
|
147
|
+
expect(getByText(/Unsubscribe/i)).toBeInTheDocument();
|
|
148
|
+
});
|
|
149
|
+
|
|
108
150
|
it('calls transformCouponTags when selectedOfferDetails and coupon tags are present', () => {
|
|
109
151
|
const spy = jest.spyOn(TagList.prototype, 'transformCouponTags');
|
|
110
152
|
initializeTagList({
|
|
@@ -95,6 +95,7 @@ export const noApprovedRcsTemplatesTitle='noApprovedRcsTemplatesTitle'
|
|
|
95
95
|
export const noApprovedRcsTemplatesDesc='noApprovedRcsTemplatesDesc'
|
|
96
96
|
|
|
97
97
|
// Archive feature
|
|
98
|
+
export const DEFAULT_ARCHIVAL_LIMIT = 25;
|
|
98
99
|
export const ARCHIVE_TEMPLATE_REQUEST = 'app/v2Containers/Templates/ARCHIVE_TEMPLATE_REQUEST';
|
|
99
100
|
export const ARCHIVE_TEMPLATE_SUCCESS = 'app/v2Containers/Templates/ARCHIVE_TEMPLATE_SUCCESS';
|
|
100
101
|
export const ARCHIVE_TEMPLATE_FAILURE = 'app/v2Containers/Templates/ARCHIVE_TEMPLATE_FAILURE';
|
|
@@ -117,7 +117,7 @@ import {
|
|
|
117
117
|
URL,
|
|
118
118
|
SITE_URL,
|
|
119
119
|
} from '../App/constants';
|
|
120
|
-
import {MAX_WHATSAPP_TEMPLATES, WARNING_WHATSAPP_TEMPLATES , ACCOUNT_MAPPING_ON_CHANNEL, noFilteredWhatsappZaloTemplatesTitle, noFilteredWhatsappZaloTemplatesDesc, noApprovedWhatsappZaloTemplatesTitle, noApprovedWhatsappTemplatesDesc, zaloDescIllustration, noApprovedRcsTemplatesTitle, noApprovedRcsTemplatesDesc, ARCHIVE_STATUS_ACTIVE, ARCHIVE_STATUS_ARCHIVED, ARCHIVE_REFRESH_TYPE_ARCHIVE, ARCHIVE_REFRESH_TYPE_UNARCHIVE} from './constants';
|
|
120
|
+
import {MAX_WHATSAPP_TEMPLATES, WARNING_WHATSAPP_TEMPLATES , ACCOUNT_MAPPING_ON_CHANNEL, noFilteredWhatsappZaloTemplatesTitle, noFilteredWhatsappZaloTemplatesDesc, noApprovedWhatsappZaloTemplatesTitle, noApprovedWhatsappTemplatesDesc, zaloDescIllustration, noApprovedRcsTemplatesTitle, noApprovedRcsTemplatesDesc, ARCHIVE_STATUS_ACTIVE, ARCHIVE_STATUS_ARCHIVED, ARCHIVE_REFRESH_TYPE_ARCHIVE, ARCHIVE_REFRESH_TYPE_UNARCHIVE, DEFAULT_ARCHIVAL_LIMIT} from './constants';
|
|
121
121
|
import { COPY_OF, EMBEDDED } from '../../constants/unified';
|
|
122
122
|
import { sanitizeFileName, exportEmailTemplateHtml } from '../../utils/downloadFile';
|
|
123
123
|
import {
|
|
@@ -152,7 +152,7 @@ import {
|
|
|
152
152
|
CAP_SPACE_16, CAP_G08, CAP_G05, CAP_SPACE_08, CAP_SPACE_12, CAP_YELLOW01,
|
|
153
153
|
} from '@capillarytech/cap-ui-library/styled/variables';
|
|
154
154
|
import { SMS_CATEGORY_TAG_STYLES, SMS_CATEGORY_LABELS } from '../../v2Components/SmsFallback/constants';
|
|
155
|
-
import { GA } from '@capillarytech/cap-ui-utils';
|
|
155
|
+
import { GA, getEnvVariable } from '@capillarytech/cap-ui-utils';
|
|
156
156
|
import { MAPP_SDK } from '../InApp/constants';
|
|
157
157
|
import injectReducer from '../../utils/injectReducer';
|
|
158
158
|
import v2TemplateReducer from './reducer';
|
|
@@ -186,6 +186,7 @@ const AccountScrollDiv = styled.div`
|
|
|
186
186
|
overflow: scroll;
|
|
187
187
|
height: calc(100vh - 18.75rem);
|
|
188
188
|
`;
|
|
189
|
+
const ARCHIVAL_LIMIT = parseInt(getEnvVariable('ARCHIVAL_LIMIT'), 10) || DEFAULT_ARCHIVAL_LIMIT;
|
|
189
190
|
const ACCOUNT_SELECTION_MODE = "accountSelectionMode";
|
|
190
191
|
const ACCOUNT_CHANGE_MODE = 'accountChangeMode';
|
|
191
192
|
const TEMPLATES_MODE = "templatesMode";
|
|
@@ -2159,10 +2160,9 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2159
2160
|
// Archive eligibility per template: Zalo never; WhatsApp/RCS not when pending/awaiting
|
|
2160
2161
|
const cardWhatsappStatus = get(template, `versions.base.content.${WHATSAPP_LOWERCASE}.status`, '');
|
|
2161
2162
|
const cardRcsStatus = get(template, 'versions.base.content.RCS.rcsContent.cardContent[0].Status', '');
|
|
2162
|
-
const
|
|
2163
|
-
const
|
|
2164
|
-
const
|
|
2165
|
-
const isAnyArchiveInProgress = isArchivalEnabled && !!(get(this.props, 'Templates.archiveInProgress') || get(this.props, 'Templates.unarchiveInProgress') || get(this.props, 'Templates.bulkArchiveInProgress') || get(this.props, 'Templates.bulkUnarchiveInProgress'));
|
|
2163
|
+
const isCardArchiveEligible = this.isChannelArchiveEligible(currentChannel, cardWhatsappStatus, cardRcsStatus);
|
|
2164
|
+
const isArchivedMode = get(this.props, 'Templates.isArchivedMode', false);
|
|
2165
|
+
const isAnyArchiveInProgress = !!(get(this.props, 'Templates.archiveInProgress') || get(this.props, 'Templates.unarchiveInProgress') || get(this.props, 'Templates.bulkArchiveInProgress') || get(this.props, 'Templates.bulkUnarchiveInProgress'));
|
|
2166
2166
|
const templateData = {
|
|
2167
2167
|
key: `${currentChannel}-card-${template?.name}`,
|
|
2168
2168
|
title: (
|
|
@@ -2307,7 +2307,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2307
2307
|
</CapMenu.Item>
|
|
2308
2308
|
)}
|
|
2309
2309
|
{/* Archive/Unarchive menu item (full mode only, not for Zalo, not for WhatsApp/RCS awaiting/pending) */}
|
|
2310
|
-
{
|
|
2310
|
+
{(() => {
|
|
2311
2311
|
const channelUp = this.state.channel.toUpperCase();
|
|
2312
2312
|
if (!this.isChannelArchiveEligible(channelUp, status, rcsStatus)) return null;
|
|
2313
2313
|
const canArchive = this.canPerform(PERMISSIONS.CREATIVE_DELETE);
|
|
@@ -2928,12 +2928,12 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2928
2928
|
|
|
2929
2929
|
const noLoaderAndSearchText = isEmpty(this.state.searchText) && !isLoading;
|
|
2930
2930
|
|
|
2931
|
-
const
|
|
2932
|
-
const isArchivedModeLocal = isArchivalEnabledLocal && get(this.props, 'Templates.isArchivedMode', false);
|
|
2931
|
+
const isArchivedModeLocal = get(this.props, 'Templates.isArchivedMode', false);
|
|
2933
2932
|
const selectedIdsLocal = get(this.props, 'Templates.selectedTemplateIds', []);
|
|
2934
2933
|
const selectedIdsArrayLocal = selectedIdsLocal && typeof selectedIdsLocal.toJS === 'function' ? selectedIdsLocal.toJS() : (Array.isArray(selectedIdsLocal) ? selectedIdsLocal : []);
|
|
2935
2934
|
const selectedCountLocal = selectedIdsArrayLocal.length;
|
|
2936
|
-
const hasSelectionLocal =
|
|
2935
|
+
const hasSelectionLocal = this.props.isFullMode && selectedCountLocal > 0;
|
|
2936
|
+
const isArchiveLimitExceeded = selectedCountLocal > ARCHIVAL_LIMIT;
|
|
2937
2937
|
|
|
2938
2938
|
return (<div>
|
|
2939
2939
|
{[WECHAT, MOBILE_PUSH, WEBPUSH, INAPP, WHATSAPP, ZALO, RCS].includes(currentChannel) && this.showAccountName()}
|
|
@@ -2945,16 +2945,21 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2945
2945
|
{this.props.intl.formatMessage(messages.templatesSelected, { count: selectedCountLocal })}
|
|
2946
2946
|
</CapLabel>
|
|
2947
2947
|
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_DELETE)}>
|
|
2948
|
-
<
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2948
|
+
<CapTooltip title={isArchiveLimitExceeded ? this.props.intl.formatMessage(messages.archiveLimitExceeded, { limit: ARCHIVAL_LIMIT }) : ''}>
|
|
2949
|
+
<span>
|
|
2950
|
+
<CapButton
|
|
2951
|
+
type="primary"
|
|
2952
|
+
className="archive-template-button"
|
|
2953
|
+
disabled={isArchiveLimitExceeded}
|
|
2954
|
+
prefix={<CapIcon type="archive" size="s" />}
|
|
2955
|
+
onClick={() => this.handleBulkArchiveAction({ ids: selectedIdsArrayLocal, count: selectedCountLocal, isUnarchive: isArchivedModeLocal })}
|
|
2956
|
+
>
|
|
2957
|
+
<span className="archive-btn-label">
|
|
2958
|
+
<FormattedMessage {...(isArchivedModeLocal ? messages.unarchiveButton : messages.archiveButton)} />
|
|
2959
|
+
</span>
|
|
2960
|
+
</CapButton>
|
|
2956
2961
|
</span>
|
|
2957
|
-
</
|
|
2962
|
+
</CapTooltip>
|
|
2958
2963
|
</NoPermissionWrapper>
|
|
2959
2964
|
<CapButton type="secondary" onClick={() => this.props.actions.clearTemplateSelection()}>
|
|
2960
2965
|
<FormattedMessage {...messages.archiveConfirmCancel} />
|
|
@@ -4195,7 +4200,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4195
4200
|
<span className="template-name" style={{ fontWeight: `${this.state.channel.toLowerCase() === 'wechat' ? '400' : '600'}` }}>
|
|
4196
4201
|
{ template && template.versions && template.versions.history && template.versions.history.length > 1 && this.state.channel.toLowerCase() !== 'mobilepush' && <i style={{fontSize: '16px', margin: '0 8px 0 0', verticalAlign: 'middle'}} className="material-icons">filter_none</i>}
|
|
4197
4202
|
{template?.name}
|
|
4198
|
-
{
|
|
4203
|
+
{template.isArchived && <CapColoredTag tagColor={CAP_G08} tagTextColor={CAP_G05} className="archived-tag">{this.props.intl.formatMessage(messages.archivedTag)}</CapColoredTag>}
|
|
4199
4204
|
</span>
|
|
4200
4205
|
{this.props.location.query.type !== EMBEDDED && <CapPopover
|
|
4201
4206
|
trigger="click"
|
|
@@ -4208,7 +4213,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4208
4213
|
</CapButton>
|
|
4209
4214
|
</NoPermissionWrapper>
|
|
4210
4215
|
</div>}
|
|
4211
|
-
{
|
|
4216
|
+
{this.props.isFullMode && isTemplateArchiveEligible && !template.isArchived && <div className="popover-action-container">
|
|
4212
4217
|
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_DELETE)}>
|
|
4213
4218
|
<CapButton
|
|
4214
4219
|
type="link"
|
|
@@ -4220,7 +4225,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4220
4225
|
</CapButton>
|
|
4221
4226
|
</NoPermissionWrapper>
|
|
4222
4227
|
</div>}
|
|
4223
|
-
{
|
|
4228
|
+
{this.props.isFullMode && isTemplateArchiveEligible && template.isArchived && <div className="popover-action-container">
|
|
4224
4229
|
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_DELETE)}>
|
|
4225
4230
|
<CapButton
|
|
4226
4231
|
type="link"
|
|
@@ -4867,11 +4872,10 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4867
4872
|
if (([WHATSAPP_LOWERCASE, ZALO_LOWERCASE, RCS_LOWERCASE].includes(this.state?.channel?.toLocaleLowerCase()) && isEmpty(this.state?.hostName))) {
|
|
4868
4873
|
isfilterContentVisisble = false;
|
|
4869
4874
|
}
|
|
4870
|
-
const
|
|
4871
|
-
const _isArchivedMode = _isArchivalEnabled && get(this.props, 'Templates.isArchivedMode', false);
|
|
4875
|
+
const _isArchivedMode = get(this.props, 'Templates.isArchivedMode', false);
|
|
4872
4876
|
const _renderSelectedIds = get(this.props, 'Templates.selectedTemplateIds', []);
|
|
4873
4877
|
const _renderSelectedIdsArray = _renderSelectedIds && typeof _renderSelectedIds.toJS === 'function' ? _renderSelectedIds.toJS() : (Array.isArray(_renderSelectedIds) ? _renderSelectedIds : []);
|
|
4874
|
-
const _renderHasSelection =
|
|
4878
|
+
const _renderHasSelection = this.props.isFullMode && _renderSelectedIdsArray.length > 0;
|
|
4875
4879
|
|
|
4876
4880
|
const useLocalTemplates = this.props.localTemplatesConfig?.useLocalTemplates;
|
|
4877
4881
|
const builtFilterContent = ((isfilterContentVisisble || [WECHAT, MOBILE_PUSH, INAPP].includes(this.state.channel.toUpperCase())) && (
|
|
@@ -5033,7 +5037,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
5033
5037
|
}
|
|
5034
5038
|
<div className="template-listing-header-actions">
|
|
5035
5039
|
{/* More (⋯) menu: full mode only, not archived mode, not Zalo (no archive support), not when selection active, archive flag enabled */}
|
|
5036
|
-
{
|
|
5040
|
+
{!_isArchivedMode && !_renderHasSelection && this.props.isFullMode && this.props.location.query.type !== EMBEDDED && channelLowerCase !== ZALO_LOWERCASE && (
|
|
5037
5041
|
<CapDropdown
|
|
5038
5042
|
trigger={['click']}
|
|
5039
5043
|
overlay={
|
|
@@ -5129,7 +5133,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
5129
5133
|
/>
|
|
5130
5134
|
|
|
5131
5135
|
{/* Archived mode header with back arrow (full mode only, archive flag enabled) */}
|
|
5132
|
-
{
|
|
5136
|
+
{this.props.isFullMode && get(this.props, 'Templates.isArchivedMode', false) && (
|
|
5133
5137
|
<CapRow type="flex" align="middle" className="archived-mode-header">
|
|
5134
5138
|
<CapIcon
|
|
5135
5139
|
type="back"
|
|
@@ -714,6 +714,10 @@ export default defineMessages({
|
|
|
714
714
|
id: `${scope}.unarchiveTemplateSuccess`,
|
|
715
715
|
defaultMessage: 'Template unarchived successfully',
|
|
716
716
|
},
|
|
717
|
+
"archiveLimitExceeded": {
|
|
718
|
+
id: `${scope}.archiveLimitExceeded`,
|
|
719
|
+
defaultMessage: 'Archive limit exceeded, it cannot be greater than {limit}.',
|
|
720
|
+
},
|
|
717
721
|
"bulkArchiveSuccess": {
|
|
718
722
|
id: `${scope}.bulkArchiveSuccess`,
|
|
719
723
|
defaultMessage: '{count} {count, plural, one {template} other {templates}} archived successfully',
|