@capillarytech/creatives-library 8.0.268 → 8.0.270
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 +1 -0
- package/package.json +1 -1
- package/services/api.js +5 -0
- package/utils/common.js +6 -0
- package/utils/imageUrlUpload.js +141 -0
- package/utils/tagValidations.js +2 -1
- package/utils/tests/transformerUtils.test.js +297 -0
- package/utils/transformerUtils.js +40 -0
- package/v2Components/CapImageUpload/constants.js +2 -0
- package/v2Components/CapImageUpload/index.js +65 -16
- package/v2Components/CapImageUpload/index.scss +4 -1
- package/v2Components/CapImageUpload/messages.js +5 -1
- package/v2Components/CapImageUrlUpload/constants.js +26 -0
- package/v2Components/CapImageUrlUpload/index.js +365 -0
- package/v2Components/CapImageUrlUpload/index.scss +35 -0
- package/v2Components/CapImageUrlUpload/messages.js +47 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +2 -2
- package/v2Components/CommonTestAndPreview/index.js +4 -15
- package/v2Components/FormBuilder/index.js +8 -8
- package/v2Containers/App/constants.js +5 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +57 -2
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -0
- package/v2Containers/CreativesContainer/constants.js +3 -0
- package/v2Containers/CreativesContainer/index.js +168 -0
- package/v2Containers/CreativesContainer/messages.js +4 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +210 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +304 -0
- package/v2Containers/FTP/index.js +1 -1
- package/v2Containers/InApp/index.js +1 -0
- package/v2Containers/Line/Container/Text/index.js +1 -0
- package/v2Containers/MobilePushNew/index.js +1 -0
- package/v2Containers/Rcs/index.js +3 -0
- package/v2Containers/SmsTrai/Edit/index.js +2 -12
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +36 -648
- package/v2Containers/Templates/ChannelTypeIllustration.js +13 -1
- package/v2Containers/Templates/_templates.scss +205 -0
- package/v2Containers/Templates/actions.js +2 -1
- package/v2Containers/Templates/constants.js +1 -0
- package/v2Containers/Templates/index.js +274 -34
- package/v2Containers/Templates/messages.js +24 -0
- package/v2Containers/Templates/reducer.js +2 -0
- package/v2Containers/Templates/tests/index.test.js +10 -0
- package/v2Containers/TemplatesV2/index.js +15 -7
- package/v2Containers/TemplatesV2/messages.js +4 -0
- package/v2Containers/Viber/index.js +1 -0
- package/v2Containers/WebPush/Create/components/BrandIconSection.js +108 -0
- package/v2Containers/WebPush/Create/components/ButtonForm.js +172 -0
- package/v2Containers/WebPush/Create/components/ButtonItem.js +101 -0
- package/v2Containers/WebPush/Create/components/ButtonList.js +145 -0
- package/v2Containers/WebPush/Create/components/ButtonsLinksSection.js +164 -0
- package/v2Containers/WebPush/Create/components/ButtonsLinksSection.test.js +463 -0
- package/v2Containers/WebPush/Create/components/FormActions.js +54 -0
- package/v2Containers/WebPush/Create/components/FormActions.test.js +163 -0
- package/v2Containers/WebPush/Create/components/MediaSection.js +142 -0
- package/v2Containers/WebPush/Create/components/MediaSection.test.js +341 -0
- package/v2Containers/WebPush/Create/components/MessageSection.js +103 -0
- package/v2Containers/WebPush/Create/components/MessageSection.test.js +268 -0
- package/v2Containers/WebPush/Create/components/NotificationTitleSection.js +87 -0
- package/v2Containers/WebPush/Create/components/NotificationTitleSection.test.js +210 -0
- package/v2Containers/WebPush/Create/components/TemplateNameSection.js +54 -0
- package/v2Containers/WebPush/Create/components/TemplateNameSection.test.js +143 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/ButtonsLinksSection.test.js.snap +86 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/FormActions.test.js.snap +16 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/MediaSection.test.js.snap +41 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +54 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/NotificationTitleSection.test.js.snap +37 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/TemplateNameSection.test.js.snap +21 -0
- package/v2Containers/WebPush/Create/components/_buttons.scss +246 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +554 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +607 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +633 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +666 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +74 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +78 -0
- package/v2Containers/WebPush/Create/hooks/useButtonManagement.js +138 -0
- package/v2Containers/WebPush/Create/hooks/useButtonManagement.test.js +406 -0
- package/v2Containers/WebPush/Create/hooks/useCharacterCount.js +30 -0
- package/v2Containers/WebPush/Create/hooks/useCharacterCount.test.js +151 -0
- package/v2Containers/WebPush/Create/hooks/useImageUpload.js +104 -0
- package/v2Containers/WebPush/Create/hooks/useImageUpload.test.js +538 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +122 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +633 -0
- package/v2Containers/WebPush/Create/index.js +1148 -0
- package/v2Containers/WebPush/Create/index.scss +134 -0
- package/v2Containers/WebPush/Create/messages.js +211 -0
- package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +228 -0
- package/v2Containers/WebPush/Create/preview/NotificationContainer.js +294 -0
- package/v2Containers/WebPush/Create/preview/PreviewContent.js +90 -0
- package/v2Containers/WebPush/Create/preview/PreviewControls.js +305 -0
- package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +25 -0
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +156 -0
- package/v2Containers/WebPush/Create/preview/assets/Light.svg +53 -0
- package/v2Containers/WebPush/Create/preview/assets/Top.svg +5 -0
- package/v2Containers/WebPush/Create/preview/assets/android-arrow-down.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/android-arrow-up.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
- package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
- package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +106 -0
- package/v2Containers/WebPush/Create/preview/assets/iOS.svg +26 -0
- package/v2Containers/WebPush/Create/preview/assets/macos-arrow-down-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/macos-triple-dot-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +18 -0
- package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +29 -0
- package/v2Containers/WebPush/Create/preview/assets/windows-close-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/windows-triple-dot-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +51 -0
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +145 -0
- package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +45 -0
- package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +68 -0
- package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +61 -0
- package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +99 -0
- package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +733 -0
- package/v2Containers/WebPush/Create/preview/components/tests/WindowsChromeExpanded.test.js +571 -0
- package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +85 -0
- package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/WindowsChromeExpanded.test.js.snap +81 -0
- package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +50 -0
- package/v2Containers/WebPush/Create/preview/constants.js +637 -0
- package/v2Containers/WebPush/Create/preview/notification-container.scss +79 -0
- package/v2Containers/WebPush/Create/preview/preview.scss +358 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +370 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +47 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_base.scss +207 -0
- package/v2Containers/WebPush/Create/preview/styles/_ios.scss +153 -0
- package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +107 -0
- package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +101 -0
- package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +229 -0
- package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +906 -0
- package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +1081 -0
- package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +723 -0
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +1327 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +131 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +112 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +144 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +129 -0
- package/v2Containers/WebPush/Create/utils/payloadBuilder.js +96 -0
- package/v2Containers/WebPush/Create/utils/payloadBuilder.test.js +396 -0
- package/v2Containers/WebPush/Create/utils/previewUtils.js +89 -0
- package/v2Containers/WebPush/Create/utils/urlValidation.js +115 -0
- package/v2Containers/WebPush/Create/utils/urlValidation.test.js +449 -0
- package/v2Containers/WebPush/Create/utils/validation.js +76 -0
- package/v2Containers/WebPush/Create/utils/validation.test.js +283 -0
- package/v2Containers/WebPush/actions.js +60 -0
- package/v2Containers/WebPush/constants.js +132 -0
- package/v2Containers/WebPush/index.js +2 -0
- package/v2Containers/WebPush/reducer.js +104 -0
- package/v2Containers/WebPush/sagas.js +119 -0
- package/v2Containers/WebPush/selectors.js +65 -0
- package/v2Containers/WebPush/tests/reducer.test.js +863 -0
- package/v2Containers/WebPush/tests/sagas.test.js +566 -0
- package/v2Containers/WebPush/tests/selectors.test.js +960 -0
- package/v2Containers/Whatsapp/index.js +1 -0
- package/v2Containers/Zalo/index.js +1 -0
- package/v2Containers/Zalo/tests/index.test.js +1 -5
|
@@ -22,6 +22,7 @@ describe('Test Templates container', () => {
|
|
|
22
22
|
const getAllTemplates = jest.fn();
|
|
23
23
|
const getUserList = jest.fn();
|
|
24
24
|
const getSenderDetails = jest.fn();
|
|
25
|
+
const resetTemplate = jest.fn();
|
|
25
26
|
let renderedComponent;
|
|
26
27
|
|
|
27
28
|
beforeEach(() => {
|
|
@@ -54,6 +55,7 @@ describe('Test Templates container', () => {
|
|
|
54
55
|
getAllTemplates,
|
|
55
56
|
getUserList,
|
|
56
57
|
getSenderDetails,
|
|
58
|
+
resetTemplate,
|
|
57
59
|
}}
|
|
58
60
|
location={{
|
|
59
61
|
pathname: `/${channel}`,
|
|
@@ -79,6 +81,8 @@ describe('Test Templates container', () => {
|
|
|
79
81
|
channel: 'WHATSAPP',
|
|
80
82
|
orgUnitId: -1,
|
|
81
83
|
});
|
|
84
|
+
// resetTemplate should be called when entering account selection mode
|
|
85
|
+
expect(resetTemplate).toHaveBeenCalled();
|
|
82
86
|
});
|
|
83
87
|
|
|
84
88
|
it('Should render temlates when whatsapp templates are passed', () => {
|
|
@@ -103,6 +107,8 @@ describe('Test Templates container', () => {
|
|
|
103
107
|
Templates: {},
|
|
104
108
|
});
|
|
105
109
|
expect(renderedComponent).toMatchSnapshot();
|
|
110
|
+
// SMS doesn't enter account selection mode, so resetTemplate shouldn't be called on mount
|
|
111
|
+
expect(resetTemplate).not.toHaveBeenCalled();
|
|
106
112
|
});
|
|
107
113
|
|
|
108
114
|
it('Should render temlates when whatsapp templates are passed in full mode', () => {
|
|
@@ -123,6 +129,8 @@ describe('Test Templates container', () => {
|
|
|
123
129
|
it('Should render correct component for zalo channel', () => {
|
|
124
130
|
RenderFunctionFor('zalo');
|
|
125
131
|
expect(renderedComponent).toMatchSnapshot();
|
|
132
|
+
// resetTemplate should be called when entering account selection mode
|
|
133
|
+
expect(resetTemplate).toHaveBeenCalled();
|
|
126
134
|
});
|
|
127
135
|
it('Should render temlates when zalo templates are passed', () => {
|
|
128
136
|
RenderFunctionFor('zalo');
|
|
@@ -201,6 +209,8 @@ describe('Test Templates container', () => {
|
|
|
201
209
|
channel: 'RCS',
|
|
202
210
|
orgUnitId: -1,
|
|
203
211
|
});
|
|
212
|
+
// resetTemplate should be called when entering account selection mode
|
|
213
|
+
expect(resetTemplate).toHaveBeenCalled();
|
|
204
214
|
});
|
|
205
215
|
|
|
206
216
|
it('Should render templates when RCS templates are passed', () => {
|
|
@@ -30,11 +30,11 @@ import FTP from '../FTP';
|
|
|
30
30
|
import Gallery from '../Assets/Gallery';
|
|
31
31
|
import withStyles from '../../hoc/withStyles';
|
|
32
32
|
import styles, { CapTabStyle } from './TemplatesV2.style';
|
|
33
|
-
import { CREATIVES_UI_VIEW, LOYALTY, WHATSAPP, RCS, LINE, EMAIL, ASSETS, JP_LOCALE_HIDE_FEATURE, ZALO, INAPP } from '../App/constants';
|
|
33
|
+
import { CREATIVES_UI_VIEW, LOYALTY, WHATSAPP, RCS, LINE, EMAIL, ASSETS, JP_LOCALE_HIDE_FEATURE, ZALO, INAPP, WEBPUSH } from '../App/constants';
|
|
34
34
|
import AccessForbidden from '../../v2Components/AccessForbidden';
|
|
35
35
|
import { getObjFromQueryParams } from '../../utils/v2common';
|
|
36
36
|
import { makeSelectAuthenticated, selectCurrentOrgDetails } from "../../v2Containers/Cap/selectors";
|
|
37
|
-
import { LOYALTY_SUPPORTED_ACTION } from "../CreativesContainer/constants";
|
|
37
|
+
import { LOYALTY_SUPPORTED_ACTION, COMMON_CHANNELS } from "../CreativesContainer/constants";
|
|
38
38
|
|
|
39
39
|
const {CapCustomCardList} = CapCustomCard;
|
|
40
40
|
|
|
@@ -65,6 +65,13 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
|
|
|
65
65
|
email: {content: <></>, tab: intl.formatMessage(messages.email), key: 'email'},
|
|
66
66
|
//'wechat': {content: this.getTemplatesComponent('wechat'), tab: 'Wechat', key: 'wechat'},
|
|
67
67
|
mPush: {content: <></>, tab: intl.formatMessage(messages.pushNotification), key: 'mobilepush'},
|
|
68
|
+
...(commonUtil.hasWebPushFeatureEnabled() ? {
|
|
69
|
+
webpush: {
|
|
70
|
+
content: <div></div>,
|
|
71
|
+
tab: intl.formatMessage(messages.webPush),
|
|
72
|
+
key: WEBPUSH,
|
|
73
|
+
}
|
|
74
|
+
} : {}),
|
|
68
75
|
viber: {content: <></>, tab: intl.formatMessage(messages.viber), key: 'viber'},
|
|
69
76
|
whatsapp: { content: <></>, tab: intl.formatMessage(messages.whatsapp), key: WHATSAPP },
|
|
70
77
|
zalo: { content: <div></div>, tab: intl.formatMessage(messages.zalo), key: ZALO },
|
|
@@ -88,7 +95,7 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
|
|
|
88
95
|
return obj;
|
|
89
96
|
}, []);
|
|
90
97
|
|
|
91
|
-
|
|
98
|
+
if (isFullMode ) {
|
|
92
99
|
filteredPanes.push({content: <div></div>, tab: intl.formatMessage(messages.gallery), key: 'assets'});
|
|
93
100
|
} else {
|
|
94
101
|
if (!channelsToHide.includes('callTask')) {
|
|
@@ -98,18 +105,19 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
|
|
|
98
105
|
filteredPanes.push({content: <></>, tab: intl.formatMessage(messages.FTP), key: 'ftp'});
|
|
99
106
|
defaultChannel = 'FTP';
|
|
100
107
|
}
|
|
101
|
-
const commonChannels = ['sms', 'email', 'wechat', 'mobilepush', 'line', 'viber', 'facebook', 'call_task', 'ftp', 'assets'];
|
|
102
108
|
|
|
109
|
+
// Create a local copy of COMMON_CHANNELS to avoid mutating the imported array
|
|
110
|
+
const channels = [...COMMON_CHANNELS];
|
|
103
111
|
const { actionName = ''} = loyaltyMetaData;
|
|
104
112
|
if (isLoyaltyModule && actionName === LOYALTY_SUPPORTED_ACTION) {
|
|
105
|
-
|
|
113
|
+
channels.push(WHATSAPP, ZALO);
|
|
106
114
|
}
|
|
107
115
|
|
|
108
|
-
// we only show channels which other than
|
|
116
|
+
// we only show channels which other than COMMON_CHANNELS
|
|
109
117
|
// if it is coming in enableNewChannels array
|
|
110
118
|
filteredPanes = filteredPanes.filter((item) => {
|
|
111
119
|
const channel = item.key;
|
|
112
|
-
if (!
|
|
120
|
+
if (!channels.includes(channel)) {
|
|
113
121
|
return enableNewChannels.includes(channel.toUpperCase());
|
|
114
122
|
}
|
|
115
123
|
return true;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
5
|
+
import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
|
|
6
|
+
import CapRadioGroup from '@capillarytech/cap-ui-library/CapRadioGroup';
|
|
7
|
+
import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
|
|
8
|
+
import CapImageUpload from '../../../../v2Components/CapImageUpload';
|
|
9
|
+
import { WEBPUSH_BRAND_ICON } from '../../../CreativesContainer/constants';
|
|
10
|
+
import {
|
|
11
|
+
BRAND_ICON_OPTIONS,
|
|
12
|
+
ALLOWED_IMAGE_EXTENSIONS_REGEX,
|
|
13
|
+
WEBPUSH_BRAND_ICON_SIZE,
|
|
14
|
+
WEBPUSH_BRAND_ICON_RECOMMENDED_DIMENSIONS,
|
|
15
|
+
} from '../../constants';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* BrandIconSection component - Brand icon/logo upload options
|
|
19
|
+
*/
|
|
20
|
+
export const BrandIconSection = ({
|
|
21
|
+
brandIconOption,
|
|
22
|
+
onBrandIconChange,
|
|
23
|
+
brandIconUpload,
|
|
24
|
+
isLocked,
|
|
25
|
+
isAnyUploadActive,
|
|
26
|
+
formatMessage,
|
|
27
|
+
messages,
|
|
28
|
+
webPush,
|
|
29
|
+
isFullMode,
|
|
30
|
+
}) => {
|
|
31
|
+
const {
|
|
32
|
+
imageSrc: brandIconSrc,
|
|
33
|
+
uploadAsset: uploadBrandIconAsset,
|
|
34
|
+
setUpdateImageSrc: setUpdateBrandIconSrc,
|
|
35
|
+
updateOnReUpload: updateOnBrandIconReUpload,
|
|
36
|
+
} = brandIconUpload;
|
|
37
|
+
|
|
38
|
+
const brandIconOptions = [
|
|
39
|
+
{ value: BRAND_ICON_OPTIONS.DONT_SHOW, label: formatMessage(messages.dontShow) },
|
|
40
|
+
{ value: BRAND_ICON_OPTIONS.UPLOAD_IMAGE, label: formatMessage(messages.uploadImage) },
|
|
41
|
+
// NOTE: Commented out due to technical blocker - "Add image URL" option for Brand icon/logo
|
|
42
|
+
// { value: BRAND_ICON_OPTIONS.ADD_IMAGE_URL, label: formatMessage(messages.addImageUrl) },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
<CapRow className="creatives-webpush-brand-icon">
|
|
48
|
+
<CapHeading type="h4" className="webpush-brand-icon">
|
|
49
|
+
<FormattedMessage {...messages.brandIconLogo} />
|
|
50
|
+
</CapHeading>
|
|
51
|
+
<CapRadioGroup
|
|
52
|
+
options={brandIconOptions}
|
|
53
|
+
value={brandIconOption}
|
|
54
|
+
onChange={onBrandIconChange}
|
|
55
|
+
disabled={isAnyUploadActive}
|
|
56
|
+
/>
|
|
57
|
+
</CapRow>
|
|
58
|
+
{brandIconOption === BRAND_ICON_OPTIONS.UPLOAD_IMAGE && (
|
|
59
|
+
<CapRow
|
|
60
|
+
className="webpush-brand-icon-upload-section"
|
|
61
|
+
style={isLocked ? { pointerEvents: 'none', opacity: 0.5 } : undefined}
|
|
62
|
+
aria-disabled={isLocked}
|
|
63
|
+
>
|
|
64
|
+
<CapImageUpload
|
|
65
|
+
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
66
|
+
imgSize={WEBPUSH_BRAND_ICON_SIZE}
|
|
67
|
+
uploadAsset={uploadBrandIconAsset}
|
|
68
|
+
isFullMode={isFullMode}
|
|
69
|
+
imageSrc={brandIconSrc}
|
|
70
|
+
updateImageSrc={setUpdateBrandIconSrc}
|
|
71
|
+
updateOnReUpload={updateOnBrandIconReUpload}
|
|
72
|
+
index={1}
|
|
73
|
+
className="cap-custom-image-upload"
|
|
74
|
+
key="webpush-brand-icon-uploaded-image"
|
|
75
|
+
imageData={webPush}
|
|
76
|
+
channel={WEBPUSH_BRAND_ICON}
|
|
77
|
+
showReUploadButton
|
|
78
|
+
recommendedDimensions={WEBPUSH_BRAND_ICON_RECOMMENDED_DIMENSIONS}
|
|
79
|
+
disabled={isLocked}
|
|
80
|
+
/>
|
|
81
|
+
</CapRow>
|
|
82
|
+
)}
|
|
83
|
+
<CapDivider />
|
|
84
|
+
</>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
BrandIconSection.propTypes = {
|
|
89
|
+
brandIconOption: PropTypes.string.isRequired,
|
|
90
|
+
onBrandIconChange: PropTypes.func.isRequired,
|
|
91
|
+
brandIconUpload: PropTypes.object.isRequired,
|
|
92
|
+
isLocked: PropTypes.bool,
|
|
93
|
+
isAnyUploadActive: PropTypes.bool,
|
|
94
|
+
formatMessage: PropTypes.func.isRequired,
|
|
95
|
+
messages: PropTypes.object.isRequired,
|
|
96
|
+
webPush: PropTypes.object,
|
|
97
|
+
isFullMode: PropTypes.bool,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
BrandIconSection.defaultProps = {
|
|
101
|
+
isLocked: false,
|
|
102
|
+
isAnyUploadActive: false,
|
|
103
|
+
webPush: {},
|
|
104
|
+
isFullMode: true,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export default BrandIconSection;
|
|
108
|
+
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
5
|
+
import CapInput from '@capillarytech/cap-ui-library/CapInput';
|
|
6
|
+
import CapButton from '@capillarytech/cap-ui-library/CapButton';
|
|
7
|
+
import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
|
|
8
|
+
import messages from '../messages';
|
|
9
|
+
import { isValidHttpUrl } from '../utils/urlValidation';
|
|
10
|
+
import { WEBPUSH_BUTTON_TYPES, BUTTON_TEXT_MAX_LENGTH } from '../../constants';
|
|
11
|
+
|
|
12
|
+
const ButtonForm = ({
|
|
13
|
+
buttonType, // 'primary' or 'secondary'
|
|
14
|
+
formatMessage,
|
|
15
|
+
onSave,
|
|
16
|
+
onCancel,
|
|
17
|
+
initialData,
|
|
18
|
+
isEditMode,
|
|
19
|
+
}) => {
|
|
20
|
+
const [buttonText, setButtonText] = useState(initialData?.text || '');
|
|
21
|
+
const [buttonUrl, setButtonUrl] = useState(initialData?.url || '');
|
|
22
|
+
const [buttonTextError, setButtonTextError] = useState('');
|
|
23
|
+
const [buttonUrlError, setButtonUrlError] = useState('');
|
|
24
|
+
|
|
25
|
+
const validateButtonText = useCallback((value) => {
|
|
26
|
+
if (!value || value.trim() === '') {
|
|
27
|
+
return formatMessage(messages.buttonTextRequired);
|
|
28
|
+
}
|
|
29
|
+
return '';
|
|
30
|
+
}, [formatMessage]);
|
|
31
|
+
|
|
32
|
+
const validateButtonUrl = useCallback((value) => {
|
|
33
|
+
if (!value || value.trim() === '') {
|
|
34
|
+
return formatMessage(messages.buttonUrlRequired);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!isValidHttpUrl(value)) {
|
|
38
|
+
return formatMessage(messages.buttonUrlInvalid);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return '';
|
|
42
|
+
}, [formatMessage]);
|
|
43
|
+
|
|
44
|
+
const handleButtonTextChange = useCallback((e) => {
|
|
45
|
+
const { value } = e.target;
|
|
46
|
+
setButtonText(value);
|
|
47
|
+
const nextError = validateButtonText(value);
|
|
48
|
+
setButtonTextError((prev) => (prev === nextError ? prev : nextError));
|
|
49
|
+
}, [validateButtonText]);
|
|
50
|
+
|
|
51
|
+
const handleButtonUrlChange = useCallback((e) => {
|
|
52
|
+
const { value } = e.target;
|
|
53
|
+
setButtonUrl(value);
|
|
54
|
+
const nextError = validateButtonUrl(value);
|
|
55
|
+
setButtonUrlError((prev) => (prev === nextError ? prev : nextError));
|
|
56
|
+
}, [validateButtonUrl]);
|
|
57
|
+
|
|
58
|
+
const handleSave = useCallback(() => {
|
|
59
|
+
const textError = validateButtonText(buttonText);
|
|
60
|
+
const urlError = validateButtonUrl(buttonUrl);
|
|
61
|
+
|
|
62
|
+
if (textError || urlError) {
|
|
63
|
+
setButtonTextError(textError);
|
|
64
|
+
setButtonUrlError(urlError);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
onSave({
|
|
69
|
+
text: buttonText.trim(),
|
|
70
|
+
url: buttonUrl.trim(),
|
|
71
|
+
type: buttonType,
|
|
72
|
+
});
|
|
73
|
+
}, [buttonText, buttonUrl, buttonType, onSave, validateButtonText, validateButtonUrl]);
|
|
74
|
+
|
|
75
|
+
const handleCancel = useCallback(() => {
|
|
76
|
+
onCancel();
|
|
77
|
+
}, [onCancel]);
|
|
78
|
+
|
|
79
|
+
const isSaveDisabled = !buttonText.trim() || !buttonUrl.trim() || buttonTextError || buttonUrlError;
|
|
80
|
+
|
|
81
|
+
const renderCharacterCountSuffix = () => {
|
|
82
|
+
const currentLength = buttonText.length;
|
|
83
|
+
const maxLength = BUTTON_TEXT_MAX_LENGTH;
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<span className="button-character-count-suffix">
|
|
87
|
+
{`${currentLength}/${maxLength}`}
|
|
88
|
+
</span>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleKeyDown = useCallback((e) => {
|
|
93
|
+
if (e.key === 'Enter' && !isSaveDisabled) {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
handleSave();
|
|
96
|
+
}
|
|
97
|
+
}, [handleSave, isSaveDisabled]);
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div className="webpush-button-form" onKeyDown={handleKeyDown}>
|
|
101
|
+
<CapRow className="button-form-row">
|
|
102
|
+
<CapHeading type="h4" className="button-form-heading">
|
|
103
|
+
<FormattedMessage {...messages.buttonText} />
|
|
104
|
+
</CapHeading>
|
|
105
|
+
<div className="button-form-field">
|
|
106
|
+
<CapInput
|
|
107
|
+
id={`webpush-button-text-input-${buttonType}`}
|
|
108
|
+
placeholder={formatMessage(messages.buttonTextPlaceholder)}
|
|
109
|
+
value={buttonText}
|
|
110
|
+
onChange={handleButtonTextChange}
|
|
111
|
+
maxLength={BUTTON_TEXT_MAX_LENGTH}
|
|
112
|
+
size="default"
|
|
113
|
+
status={buttonTextError ? 'error' : ''}
|
|
114
|
+
help={buttonTextError || ''}
|
|
115
|
+
suffix={renderCharacterCountSuffix()}
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
</CapRow>
|
|
119
|
+
<CapRow className="button-form-row">
|
|
120
|
+
<CapHeading type="h4" className="button-form-heading">
|
|
121
|
+
<FormattedMessage {...messages.buttonUrlLabel} />
|
|
122
|
+
</CapHeading>
|
|
123
|
+
<CapInput
|
|
124
|
+
id={`webpush-button-url-input-${buttonType}`}
|
|
125
|
+
placeholder={formatMessage(messages.buttonUrlPlaceholder)}
|
|
126
|
+
value={buttonUrl}
|
|
127
|
+
onChange={handleButtonUrlChange}
|
|
128
|
+
size="default"
|
|
129
|
+
status={buttonUrlError ? 'error' : ''}
|
|
130
|
+
help={buttonUrlError || ''}
|
|
131
|
+
/>
|
|
132
|
+
</CapRow>
|
|
133
|
+
<CapRow className="button-form-actions">
|
|
134
|
+
<CapButton
|
|
135
|
+
type="primary"
|
|
136
|
+
onClick={handleSave}
|
|
137
|
+
disabled={isSaveDisabled}
|
|
138
|
+
className="button-form-save"
|
|
139
|
+
>
|
|
140
|
+
<FormattedMessage {...messages.saveButton} />
|
|
141
|
+
</CapButton>
|
|
142
|
+
<CapButton
|
|
143
|
+
type="secondary"
|
|
144
|
+
onClick={handleCancel}
|
|
145
|
+
className="button-form-cancel"
|
|
146
|
+
>
|
|
147
|
+
<FormattedMessage {...(isEditMode ? messages.cancelButton : messages.deleteButton)} />
|
|
148
|
+
</CapButton>
|
|
149
|
+
</CapRow>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
ButtonForm.propTypes = {
|
|
155
|
+
buttonType: PropTypes.oneOf([WEBPUSH_BUTTON_TYPES.PRIMARY, WEBPUSH_BUTTON_TYPES.SECONDARY]).isRequired,
|
|
156
|
+
formatMessage: PropTypes.func.isRequired,
|
|
157
|
+
onSave: PropTypes.func.isRequired,
|
|
158
|
+
onCancel: PropTypes.func.isRequired,
|
|
159
|
+
initialData: PropTypes.shape({
|
|
160
|
+
text: PropTypes.string,
|
|
161
|
+
url: PropTypes.string,
|
|
162
|
+
}),
|
|
163
|
+
isEditMode: PropTypes.bool,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
ButtonForm.defaultProps = {
|
|
167
|
+
initialData: null,
|
|
168
|
+
isEditMode: false,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export default ButtonForm;
|
|
172
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
4
|
+
import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
|
|
5
|
+
import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
6
|
+
|
|
7
|
+
const ButtonItem = ({
|
|
8
|
+
button,
|
|
9
|
+
index,
|
|
10
|
+
onEdit,
|
|
11
|
+
onDelete,
|
|
12
|
+
onDragStart,
|
|
13
|
+
onDragOver,
|
|
14
|
+
onDrop,
|
|
15
|
+
onDragEnd,
|
|
16
|
+
disabled,
|
|
17
|
+
}) => {
|
|
18
|
+
const handleDragStart = (e) => {
|
|
19
|
+
if (disabled) return;
|
|
20
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
21
|
+
e.dataTransfer.setData('text/html', e.currentTarget);
|
|
22
|
+
onDragStart(index);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const handleDragOver = (e) => {
|
|
26
|
+
if (disabled) return;
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
e.dataTransfer.dropEffect = 'move';
|
|
29
|
+
onDragOver(index);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const handleDrop = (e) => {
|
|
33
|
+
if (disabled) return;
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
e.stopPropagation();
|
|
36
|
+
onDrop(index);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div
|
|
41
|
+
className={`webpush-button-item ${disabled ? 'disabled' : ''}`}
|
|
42
|
+
draggable={!disabled}
|
|
43
|
+
onDragStart={handleDragStart}
|
|
44
|
+
onDragOver={handleDragOver}
|
|
45
|
+
onDrop={handleDrop}
|
|
46
|
+
onDragEnd={onDragEnd}
|
|
47
|
+
>
|
|
48
|
+
<CapRow align="middle" className="button-item-content">
|
|
49
|
+
<CapColumn span={1} className="button-item-drag-handle">
|
|
50
|
+
<CapIcon type="drag" className="drag-icon" />
|
|
51
|
+
</CapColumn>
|
|
52
|
+
<CapColumn span={1} className="button-item-icon">
|
|
53
|
+
<CapIcon type="link" className="link-icon" />
|
|
54
|
+
</CapColumn>
|
|
55
|
+
<CapColumn span={14} className="button-item-info">
|
|
56
|
+
<div className="button-item-text-row">
|
|
57
|
+
<span className="button-item-text">{button.text}</span>
|
|
58
|
+
</div>
|
|
59
|
+
</CapColumn>
|
|
60
|
+
<CapColumn span={8} className="button-item-actions">
|
|
61
|
+
<div className="button-item-url">{button.url}</div>
|
|
62
|
+
<div className="action-icons">
|
|
63
|
+
<CapIcon
|
|
64
|
+
type="edit"
|
|
65
|
+
className="action-icon"
|
|
66
|
+
onClick={() => !disabled && onEdit(index)}
|
|
67
|
+
/>
|
|
68
|
+
<CapIcon
|
|
69
|
+
type="delete"
|
|
70
|
+
className="action-icon delete-icon"
|
|
71
|
+
onClick={() => !disabled && onDelete(index)}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
</CapColumn>
|
|
75
|
+
</CapRow>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
ButtonItem.propTypes = {
|
|
81
|
+
button: PropTypes.shape({
|
|
82
|
+
text: PropTypes.string.isRequired,
|
|
83
|
+
url: PropTypes.string.isRequired,
|
|
84
|
+
type: PropTypes.string.isRequired,
|
|
85
|
+
}).isRequired,
|
|
86
|
+
index: PropTypes.number.isRequired,
|
|
87
|
+
onEdit: PropTypes.func.isRequired,
|
|
88
|
+
onDelete: PropTypes.func.isRequired,
|
|
89
|
+
onDragStart: PropTypes.func.isRequired,
|
|
90
|
+
onDragOver: PropTypes.func.isRequired,
|
|
91
|
+
onDrop: PropTypes.func.isRequired,
|
|
92
|
+
onDragEnd: PropTypes.func.isRequired,
|
|
93
|
+
disabled: PropTypes.bool,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
ButtonItem.defaultProps = {
|
|
97
|
+
disabled: false,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export default ButtonItem;
|
|
101
|
+
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
5
|
+
import CapButton from '@capillarytech/cap-ui-library/CapButton';
|
|
6
|
+
import ButtonItem from './ButtonItem';
|
|
7
|
+
import messages from '../messages';
|
|
8
|
+
|
|
9
|
+
const ButtonList = ({
|
|
10
|
+
buttons,
|
|
11
|
+
onEdit,
|
|
12
|
+
onDelete,
|
|
13
|
+
onReorder,
|
|
14
|
+
onAddPrimary,
|
|
15
|
+
onAddSecondary,
|
|
16
|
+
showAddPrimary,
|
|
17
|
+
showAddSecondary,
|
|
18
|
+
disabled,
|
|
19
|
+
disableSecondaryButton,
|
|
20
|
+
isInlineFormVisible,
|
|
21
|
+
inlineFormIndex,
|
|
22
|
+
renderInlineForm,
|
|
23
|
+
}) => {
|
|
24
|
+
const [draggedIndex, setDraggedIndex] = useState(null);
|
|
25
|
+
|
|
26
|
+
const handleDragStart = (index) => {
|
|
27
|
+
if (disabled) return;
|
|
28
|
+
setDraggedIndex(index);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleDragOver = (index) => {
|
|
32
|
+
if (disabled) return;
|
|
33
|
+
if (draggedIndex === null || draggedIndex === index) return;
|
|
34
|
+
|
|
35
|
+
// Reorder buttons
|
|
36
|
+
onReorder(draggedIndex, index);
|
|
37
|
+
setDraggedIndex(index);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const handleDrop = () => {
|
|
41
|
+
if (disabled) return;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleDragEnd = () => {
|
|
45
|
+
if (disabled) return;
|
|
46
|
+
setDraggedIndex(null);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const shouldRenderInlineForm = isInlineFormVisible
|
|
50
|
+
&& typeof inlineFormIndex === 'number'
|
|
51
|
+
&& inlineFormIndex >= 0;
|
|
52
|
+
|
|
53
|
+
// Don't render the container if there are no buttons
|
|
54
|
+
if (buttons.length === 0) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className="webpush-button-list">
|
|
60
|
+
{buttons.map((button, index) => {
|
|
61
|
+
if (shouldRenderInlineForm && inlineFormIndex === index && typeof renderInlineForm === 'function') {
|
|
62
|
+
return (
|
|
63
|
+
<div key={`button-inline-form-${button.id}`} className="button-inline-form">
|
|
64
|
+
{renderInlineForm()}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
// Only disable items that are not being edited
|
|
69
|
+
const isItemDisabled = disabled || (isInlineFormVisible && inlineFormIndex !== null && inlineFormIndex !== index);
|
|
70
|
+
return (
|
|
71
|
+
<ButtonItem
|
|
72
|
+
key={button.id}
|
|
73
|
+
button={button}
|
|
74
|
+
index={index}
|
|
75
|
+
onEdit={onEdit}
|
|
76
|
+
onDelete={onDelete}
|
|
77
|
+
onDragStart={handleDragStart}
|
|
78
|
+
onDragOver={handleDragOver}
|
|
79
|
+
onDrop={handleDrop}
|
|
80
|
+
onDragEnd={handleDragEnd}
|
|
81
|
+
disabled={isItemDisabled}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
})}
|
|
85
|
+
{showAddPrimary && (
|
|
86
|
+
<CapRow className="button-list-add-button">
|
|
87
|
+
<CapButton
|
|
88
|
+
type="flat"
|
|
89
|
+
onClick={onAddPrimary}
|
|
90
|
+
className="add-primary-button button-add-trigger"
|
|
91
|
+
icon="plus"
|
|
92
|
+
disabled={disabled}
|
|
93
|
+
>
|
|
94
|
+
<FormattedMessage {...messages.addPrimaryButton} />
|
|
95
|
+
</CapButton>
|
|
96
|
+
</CapRow>
|
|
97
|
+
)}
|
|
98
|
+
{showAddSecondary && (
|
|
99
|
+
<CapRow className="button-list-add-button">
|
|
100
|
+
<CapButton
|
|
101
|
+
type="flat"
|
|
102
|
+
onClick={onAddSecondary}
|
|
103
|
+
className="add-secondary-button button-add-trigger"
|
|
104
|
+
icon="plus"
|
|
105
|
+
disabled={disableSecondaryButton || disabled}
|
|
106
|
+
>
|
|
107
|
+
<FormattedMessage {...messages.addSecondaryButton} />
|
|
108
|
+
</CapButton>
|
|
109
|
+
</CapRow>
|
|
110
|
+
)}
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
ButtonList.propTypes = {
|
|
116
|
+
buttons: PropTypes.arrayOf(PropTypes.shape({
|
|
117
|
+
id: PropTypes.string.isRequired,
|
|
118
|
+
text: PropTypes.string.isRequired,
|
|
119
|
+
url: PropTypes.string.isRequired,
|
|
120
|
+
type: PropTypes.string.isRequired,
|
|
121
|
+
})).isRequired,
|
|
122
|
+
onEdit: PropTypes.func.isRequired,
|
|
123
|
+
onDelete: PropTypes.func.isRequired,
|
|
124
|
+
onReorder: PropTypes.func.isRequired,
|
|
125
|
+
onAddPrimary: PropTypes.func.isRequired,
|
|
126
|
+
onAddSecondary: PropTypes.func.isRequired,
|
|
127
|
+
showAddPrimary: PropTypes.bool.isRequired,
|
|
128
|
+
showAddSecondary: PropTypes.bool.isRequired,
|
|
129
|
+
disabled: PropTypes.bool,
|
|
130
|
+
disableSecondaryButton: PropTypes.bool,
|
|
131
|
+
isInlineFormVisible: PropTypes.bool,
|
|
132
|
+
inlineFormIndex: PropTypes.number,
|
|
133
|
+
renderInlineForm: PropTypes.func,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
ButtonList.defaultProps = {
|
|
137
|
+
disabled: false,
|
|
138
|
+
disableSecondaryButton: false,
|
|
139
|
+
isInlineFormVisible: false,
|
|
140
|
+
inlineFormIndex: null,
|
|
141
|
+
renderInlineForm: null,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export default ButtonList;
|
|
145
|
+
|