@capillarytech/creatives-library 8.0.241 → 8.0.242-alpha.0

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.
Files changed (119) hide show
  1. package/package.json +1 -1
  2. package/sagas/__tests__/assetPolling.test.js +607 -0
  3. package/sagas/assetPolling.js +156 -0
  4. package/services/api.js +16 -0
  5. package/services/tests/api.test.js +124 -0
  6. package/translations/en.json +1 -0
  7. package/utils/assetStatusConstants.js +12 -0
  8. package/utils/asyncAssetUpload.js +161 -0
  9. package/utils/tests/asyncAssetUpload.test.js +292 -0
  10. package/utils/transformerUtils.js +42 -0
  11. package/v2Components/CapImageUpload/constants.js +2 -0
  12. package/v2Components/CapImageUpload/index.js +54 -14
  13. package/v2Components/CapImageUpload/index.scss +4 -1
  14. package/v2Components/CapImageUpload/messages.js +4 -0
  15. package/v2Components/CapImageUrlUpload/constants.js +19 -0
  16. package/v2Components/CapImageUrlUpload/index.js +455 -0
  17. package/v2Components/CapImageUrlUpload/index.scss +35 -0
  18. package/v2Components/CapImageUrlUpload/messages.js +47 -0
  19. package/v2Containers/App/constants.js +5 -0
  20. package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +1 -0
  21. package/v2Containers/CreativesContainer/SlideBoxContent.js +57 -2
  22. package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -0
  23. package/v2Containers/CreativesContainer/constants.js +2 -0
  24. package/v2Containers/CreativesContainer/index.js +152 -0
  25. package/v2Containers/CreativesContainer/messages.js +4 -0
  26. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
  27. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
  28. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +25 -0
  29. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +18 -0
  30. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +46 -0
  31. package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +4 -0
  32. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +8 -0
  33. package/v2Containers/Templates/ChannelTypeIllustration.js +13 -1
  34. package/v2Containers/Templates/_templates.scss +203 -0
  35. package/v2Containers/Templates/actions.js +2 -1
  36. package/v2Containers/Templates/constants.js +1 -0
  37. package/v2Containers/Templates/index.js +273 -30
  38. package/v2Containers/Templates/messages.js +24 -0
  39. package/v2Containers/Templates/reducer.js +2 -0
  40. package/v2Containers/Templates/tests/index.test.js +10 -0
  41. package/v2Containers/TemplatesV2/index.js +3 -2
  42. package/v2Containers/TemplatesV2/messages.js +4 -0
  43. package/v2Containers/WebPush/Create/components/ButtonForm.js +175 -0
  44. package/v2Containers/WebPush/Create/components/ButtonItem.js +101 -0
  45. package/v2Containers/WebPush/Create/components/ButtonList.js +144 -0
  46. package/v2Containers/WebPush/Create/components/_buttons.scss +246 -0
  47. package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +554 -0
  48. package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +607 -0
  49. package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +633 -0
  50. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +666 -0
  51. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +74 -0
  52. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +80 -0
  53. package/v2Containers/WebPush/Create/index.js +1755 -0
  54. package/v2Containers/WebPush/Create/index.scss +123 -0
  55. package/v2Containers/WebPush/Create/messages.js +199 -0
  56. package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +241 -0
  57. package/v2Containers/WebPush/Create/preview/NotificationContainer.js +290 -0
  58. package/v2Containers/WebPush/Create/preview/PreviewContent.js +81 -0
  59. package/v2Containers/WebPush/Create/preview/PreviewControls.js +240 -0
  60. package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +23 -0
  61. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +144 -0
  62. package/v2Containers/WebPush/Create/preview/assets/Light.svg +53 -0
  63. package/v2Containers/WebPush/Create/preview/assets/Top.svg +5 -0
  64. package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
  65. package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
  66. package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +106 -0
  67. package/v2Containers/WebPush/Create/preview/assets/iOS.svg +26 -0
  68. package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +18 -0
  69. package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +29 -0
  70. package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +44 -0
  71. package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +110 -0
  72. package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +45 -0
  73. package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +72 -0
  74. package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +55 -0
  75. package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +70 -0
  76. package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +512 -0
  77. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +77 -0
  78. package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +527 -0
  79. package/v2Containers/WebPush/Create/preview/constants.js +162 -0
  80. package/v2Containers/WebPush/Create/preview/notification-container.scss +104 -0
  81. package/v2Containers/WebPush/Create/preview/preview.scss +409 -0
  82. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +300 -0
  83. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +12 -0
  84. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +12 -0
  85. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +12 -0
  86. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +303 -0
  87. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +11 -0
  88. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +11 -0
  89. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +11 -0
  90. package/v2Containers/WebPush/Create/preview/styles/_base.scss +188 -0
  91. package/v2Containers/WebPush/Create/preview/styles/_ios.scss +106 -0
  92. package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +107 -0
  93. package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +75 -0
  94. package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +174 -0
  95. package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +909 -0
  96. package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +1077 -0
  97. package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +723 -0
  98. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +943 -0
  99. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +128 -0
  100. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +121 -0
  101. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +144 -0
  102. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +127 -0
  103. package/v2Containers/WebPush/Create/utils/urlValidation.js +116 -0
  104. package/v2Containers/WebPush/Create/utils/urlValidation.test.js +449 -0
  105. package/v2Containers/WebPush/actions.js +60 -0
  106. package/v2Containers/WebPush/constants.js +108 -0
  107. package/v2Containers/WebPush/index.js +2 -0
  108. package/v2Containers/WebPush/reducer.js +104 -0
  109. package/v2Containers/WebPush/sagas.js +119 -0
  110. package/v2Containers/WebPush/selectors.js +65 -0
  111. package/v2Containers/WebPush/tests/reducer.test.js +863 -0
  112. package/v2Containers/WebPush/tests/sagas.test.js +566 -0
  113. package/v2Containers/WebPush/tests/selectors.test.js +960 -0
  114. package/v2Containers/Whatsapp/constants.js +9 -0
  115. package/v2Containers/Whatsapp/reducer.js +34 -5
  116. package/v2Containers/Whatsapp/sagas.js +61 -10
  117. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +132 -0
  118. package/v2Containers/Whatsapp/tests/reducer.test.js +188 -0
  119. package/v2Containers/Whatsapp/tests/saga.test.js +420 -7
@@ -30,7 +30,7 @@ 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";
@@ -65,6 +65,7 @@ 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
+ webPush: { content: <div></div>, tab: intl.formatMessage(messages.webPush), key: WEBPUSH },
68
69
  viber: {content: <></>, tab: intl.formatMessage(messages.viber), key: 'viber'},
69
70
  whatsapp: { content: <></>, tab: intl.formatMessage(messages.whatsapp), key: WHATSAPP },
70
71
  zalo: { content: <div></div>, tab: intl.formatMessage(messages.zalo), key: ZALO },
@@ -98,7 +99,7 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
98
99
  filteredPanes.push({content: <></>, tab: intl.formatMessage(messages.FTP), key: 'ftp'});
99
100
  defaultChannel = 'FTP';
100
101
  }
101
- const commonChannels = ['sms', 'email', 'wechat', 'mobilepush', 'line', 'viber', 'facebook', 'call_task', 'ftp', 'assets'];
102
+ const commonChannels = ['sms', 'email', 'wechat', 'mobilepush', 'webpush', 'line', 'viber', 'facebook', 'call_task', 'ftp', 'assets'];
102
103
 
103
104
  const { actionName = ''} = loyaltyMetaData;
104
105
  if (isLoyaltyModule && actionName === LOYALTY_SUPPORTED_ACTION) {
@@ -86,4 +86,8 @@ export default defineMessages({
86
86
  id: `creatives.containersV2.TemplatesV2.inapp`,
87
87
  defaultMessage: 'In app message',
88
88
  },
89
+ webPush: {
90
+ id: `creatives.containersV2.TemplatesV2.webPush`,
91
+ defaultMessage: 'Web Push',
92
+ },
89
93
  });
@@ -0,0 +1,175 @@
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 } from '../../constants';
11
+
12
+ const BUTTON_TEXT_MAX_LENGTH = 20;
13
+
14
+ const ButtonForm = ({
15
+ buttonType, // 'primary' or 'secondary'
16
+ formatMessage,
17
+ onSave,
18
+ onCancel,
19
+ initialData,
20
+ isEditMode,
21
+ }) => {
22
+ const [buttonText, setButtonText] = useState(initialData?.text || '');
23
+ const [buttonUrl, setButtonUrl] = useState(initialData?.url || '');
24
+ const [buttonTextError, setButtonTextError] = useState('');
25
+ const [buttonUrlError, setButtonUrlError] = useState('');
26
+
27
+ const validateButtonText = useCallback((value) => {
28
+ if (!value || value.trim() === '') {
29
+ return formatMessage(messages.buttonTextRequired);
30
+ }
31
+ return '';
32
+ }, [formatMessage]);
33
+
34
+ const validateButtonUrl = useCallback((value) => {
35
+ if (!value || value.trim() === '') {
36
+ return formatMessage(messages.buttonUrlRequired);
37
+ }
38
+
39
+ if (!isValidHttpUrl(value)) {
40
+ return formatMessage(messages.buttonUrlInvalid);
41
+ }
42
+
43
+ return '';
44
+ }, [formatMessage]);
45
+
46
+ const handleButtonTextChange = useCallback((e) => {
47
+ const { value } = e.target;
48
+ setButtonText(value);
49
+ const nextError = validateButtonText(value);
50
+ setButtonTextError((prev) => (prev === nextError ? prev : nextError));
51
+ }, [validateButtonText]);
52
+
53
+ const handleButtonUrlChange = useCallback((e) => {
54
+ const { value } = e.target;
55
+ setButtonUrl(value);
56
+ const nextError = validateButtonUrl(value);
57
+ setButtonUrlError((prev) => (prev === nextError ? prev : nextError));
58
+ }, [validateButtonUrl]);
59
+
60
+ const handleSave = useCallback(() => {
61
+ const textError = validateButtonText(buttonText);
62
+ const urlError = validateButtonUrl(buttonUrl);
63
+
64
+ if (textError || urlError) {
65
+ setButtonTextError(textError);
66
+ setButtonUrlError(urlError);
67
+ return;
68
+ }
69
+
70
+ onSave({
71
+ text: buttonText.trim(),
72
+ url: buttonUrl.trim(),
73
+ type: buttonType,
74
+ });
75
+ }, [buttonText, buttonUrl, buttonType, onSave, validateButtonText, validateButtonUrl]);
76
+
77
+ const handleCancel = useCallback(() => {
78
+ onCancel();
79
+ }, [onCancel]);
80
+
81
+ const isSaveDisabled = !buttonText.trim() || !buttonUrl.trim() || buttonTextError || buttonUrlError;
82
+
83
+ const renderCharacterCountSuffix = () => {
84
+ const currentLength = buttonText.length;
85
+ const maxLength = BUTTON_TEXT_MAX_LENGTH;
86
+
87
+ return (
88
+ <span className="button-character-count-suffix">
89
+ {`${currentLength}/${maxLength}`}
90
+ </span>
91
+ );
92
+ };
93
+
94
+ const handleKeyDown = useCallback((e) => {
95
+ if (e.key === 'Enter' && !isSaveDisabled) {
96
+ e.preventDefault();
97
+ handleSave();
98
+ }
99
+ }, [handleSave, isSaveDisabled]);
100
+
101
+ return (
102
+ <div className="webpush-button-form" onKeyDown={handleKeyDown}>
103
+ <CapRow className="button-form-row">
104
+ <CapHeading type="h4" className="button-form-heading">
105
+ <FormattedMessage {...messages.buttonText} />
106
+ </CapHeading>
107
+ <div className="button-form-field">
108
+ {/* TODO: Enable "+ Add labels" control once label functionality is ready */}
109
+ <CapInput
110
+ id={`webpush-button-text-input-${buttonType}`}
111
+ placeholder={formatMessage(messages.buttonTextPlaceholder)}
112
+ value={buttonText}
113
+ onChange={handleButtonTextChange}
114
+ maxLength={BUTTON_TEXT_MAX_LENGTH}
115
+ size="default"
116
+ status={buttonTextError ? 'error' : ''}
117
+ help={buttonTextError || ''}
118
+ suffix={renderCharacterCountSuffix()}
119
+ />
120
+ </div>
121
+ </CapRow>
122
+ <CapRow className="button-form-row">
123
+ <CapHeading type="h4" className="button-form-heading">
124
+ <FormattedMessage {...messages.buttonUrlLabel} />
125
+ </CapHeading>
126
+ <CapInput
127
+ id={`webpush-button-url-input-${buttonType}`}
128
+ placeholder={formatMessage(messages.buttonUrlPlaceholder)}
129
+ value={buttonUrl}
130
+ onChange={handleButtonUrlChange}
131
+ size="default"
132
+ status={buttonUrlError ? 'error' : ''}
133
+ help={buttonUrlError || ''}
134
+ />
135
+ </CapRow>
136
+ <CapRow className="button-form-actions">
137
+ <CapButton
138
+ type="primary"
139
+ onClick={handleSave}
140
+ disabled={isSaveDisabled}
141
+ className="button-form-save"
142
+ >
143
+ <FormattedMessage {...messages.saveButton} />
144
+ </CapButton>
145
+ <CapButton
146
+ type="secondary"
147
+ onClick={handleCancel}
148
+ className="button-form-cancel"
149
+ >
150
+ <FormattedMessage {...(isEditMode ? messages.cancelButton : messages.deleteButton)} />
151
+ </CapButton>
152
+ </CapRow>
153
+ </div>
154
+ );
155
+ };
156
+
157
+ ButtonForm.propTypes = {
158
+ buttonType: PropTypes.oneOf([WEBPUSH_BUTTON_TYPES.PRIMARY, WEBPUSH_BUTTON_TYPES.SECONDARY]).isRequired,
159
+ formatMessage: PropTypes.func.isRequired,
160
+ onSave: PropTypes.func.isRequired,
161
+ onCancel: PropTypes.func.isRequired,
162
+ initialData: PropTypes.shape({
163
+ text: PropTypes.string,
164
+ url: PropTypes.string,
165
+ }),
166
+ isEditMode: PropTypes.bool,
167
+ };
168
+
169
+ ButtonForm.defaultProps = {
170
+ initialData: null,
171
+ isEditMode: false,
172
+ };
173
+
174
+ export default ButtonForm;
175
+
@@ -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,144 @@
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-${index}`} 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-${index}`}
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
+ text: PropTypes.string.isRequired,
118
+ url: PropTypes.string.isRequired,
119
+ type: PropTypes.string.isRequired,
120
+ })).isRequired,
121
+ onEdit: PropTypes.func.isRequired,
122
+ onDelete: PropTypes.func.isRequired,
123
+ onReorder: PropTypes.func.isRequired,
124
+ onAddPrimary: PropTypes.func.isRequired,
125
+ onAddSecondary: PropTypes.func.isRequired,
126
+ showAddPrimary: PropTypes.bool.isRequired,
127
+ showAddSecondary: PropTypes.bool.isRequired,
128
+ disabled: PropTypes.bool,
129
+ disableSecondaryButton: PropTypes.bool,
130
+ isInlineFormVisible: PropTypes.bool,
131
+ inlineFormIndex: PropTypes.number,
132
+ renderInlineForm: PropTypes.func,
133
+ };
134
+
135
+ ButtonList.defaultProps = {
136
+ disabled: false,
137
+ disableSecondaryButton: false,
138
+ isInlineFormVisible: false,
139
+ inlineFormIndex: null,
140
+ renderInlineForm: null,
141
+ };
142
+
143
+ export default ButtonList;
144
+
@@ -0,0 +1,246 @@
1
+ @import '~@capillarytech/cap-ui-library/styles/_variables';
2
+
3
+ // Button components styles for Web Push Create
4
+
5
+ .webpush-button-form {
6
+ border: 1px solid $CAP_G07;
7
+ border-radius: $CAP_SPACE_08;
8
+ padding: $CAP_SPACE_20;
9
+ margin-bottom: $CAP_SPACE_16;
10
+
11
+ .button-form-row {
12
+ margin-bottom: $CAP_SPACE_16;
13
+
14
+ &:last-child {
15
+ margin-bottom: 0;
16
+ }
17
+ }
18
+
19
+ .button-form-heading {
20
+ margin-bottom: $CAP_SPACE_08;
21
+ font-weight: 600;
22
+ }
23
+
24
+ .button-form-field {
25
+ position: relative;
26
+ }
27
+
28
+ .button-character-count-suffix {
29
+ font-size: $FONT_SIZE_S;
30
+ color: $FONT_COLOR_03;
31
+ display: inline-flex;
32
+ align-items: center;
33
+ }
34
+
35
+ .button-form-actions {
36
+ display: flex;
37
+ gap: $CAP_SPACE_12;
38
+ margin-top: $CAP_SPACE_20;
39
+
40
+ .button-form-save {
41
+ background-color: map-get($CAP_PRIMARY, base);
42
+ border-color: map-get($CAP_PRIMARY, base);
43
+
44
+ &:hover:not(:disabled) {
45
+ background-color: map-get($CAP_PRIMARY, hover);
46
+ border-color: map-get($CAP_PRIMARY, hover);
47
+ }
48
+
49
+ &:disabled {
50
+ opacity: 0.5;
51
+ cursor: not-allowed;
52
+ }
53
+ }
54
+
55
+ .button-form-cancel {
56
+ background-color: $CAP_G12;
57
+ border-color: $CAP_COLOR_16;
58
+ color: $FONT_COLOR_01;
59
+
60
+ &:hover {
61
+ background-color: $CAP_COLOR_16;
62
+ border-color: $CAP_G06;
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ .webpush-button-list {
69
+ .button-list-add-button {
70
+ margin-top: $CAP_SPACE_12;
71
+ }
72
+ }
73
+
74
+ .button-add-controls {
75
+ display: flex;
76
+ gap: $CAP_SPACE_12;
77
+ flex-wrap: wrap;
78
+ margin-top: $CAP_SPACE_16;
79
+ }
80
+
81
+ // High specificity selectors to override Cap UI library styles
82
+ // Target both CapButton and Ant Design button classes that may be on the same element
83
+ .button-add-controls button.button-add-trigger,
84
+ .button-add-controls .button-add-trigger.cap-button,
85
+ .button-add-controls .button-add-trigger.ant-btn,
86
+ .button-add-controls .button-add-trigger.cap-button-flat,
87
+ .button-add-controls .button-add-trigger.ant-btn-flat,
88
+ .webpush-button-list button.button-add-trigger,
89
+ .webpush-button-list .button-add-trigger.cap-button,
90
+ .webpush-button-list .button-add-trigger.ant-btn,
91
+ .webpush-button-list .button-add-trigger.cap-button-flat,
92
+ .webpush-button-list .button-add-trigger.ant-btn-flat {
93
+ border: none;
94
+ background: transparent;
95
+ color: map-get($CAP_SECONDARY, base);
96
+ font-weight: $FONT_WEIGHT_MEDIUM;
97
+ border-radius: $CAP_SPACE_08;
98
+ padding: $CAP_SPACE_08 $CAP_SPACE_16;
99
+ display: inline-flex;
100
+ align-items: center;
101
+ gap: $CAP_SPACE_08;
102
+
103
+ &:hover:not(:disabled) {
104
+ background: $CAP_G09;
105
+ }
106
+
107
+ &:disabled {
108
+ color: $CAP_G05;
109
+ cursor: not-allowed;
110
+ background: transparent;
111
+ }
112
+ }
113
+
114
+ .button-inline-form {
115
+ margin-bottom: $CAP_SPACE_12;
116
+ }
117
+
118
+ .webpush-button-item {
119
+ border: 1px solid $CAP_G07;
120
+ border-radius: 0.571rem;
121
+ padding: 0.7rem 0.857rem;
122
+ margin-bottom: 0.857rem;
123
+ background: $CAP_WHITE;
124
+ transition: all 0.2s ease;
125
+ cursor: move;
126
+
127
+ &:hover {
128
+ border-color: map-get($CAP_SECONDARY, base);
129
+ box-shadow: 0 0.142rem 0.571rem rgba(0, 0, 0, 0.08);
130
+ }
131
+
132
+ &[draggable="true"] {
133
+ cursor: move;
134
+ }
135
+
136
+ .button-item-content {
137
+ align-items: center;
138
+ }
139
+
140
+ .button-item-drag-handle {
141
+ display: flex;
142
+ align-items: center;
143
+ justify-content: center;
144
+ cursor: move;
145
+ padding-right: $CAP_SPACE_08;
146
+
147
+ .drag-icon {
148
+ font-size: 1rem;
149
+ color: $FONT_COLOR_01;
150
+ font-weight: 700;
151
+ transform: rotate(90deg);
152
+ display: inline-block;
153
+ }
154
+ }
155
+
156
+ .button-item-icon {
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: flex-start;
160
+ padding-right: $CAP_SPACE_08;
161
+
162
+ .link-icon {
163
+ font-size: $FONT_SIZE_L;
164
+ color: $FONT_COLOR_01;
165
+ }
166
+ }
167
+
168
+ .button-item-info {
169
+ display: flex;
170
+ align-items: center;
171
+ padding-right: $CAP_SPACE_12;
172
+
173
+ .button-item-text-row {
174
+ display: flex;
175
+ align-items: center;
176
+ width: 100%;
177
+
178
+ .button-item-text {
179
+ font-size: 13px;
180
+ font-weight: $FONT_WEIGHT_MEDIUM;
181
+ color: $FONT_COLOR_01;
182
+ overflow: hidden;
183
+ text-overflow: ellipsis;
184
+ white-space: nowrap;
185
+ max-width: 100%;
186
+ }
187
+ }
188
+ }
189
+
190
+ .button-item-actions {
191
+ display: flex;
192
+ align-items: center;
193
+ justify-content: flex-end;
194
+ gap: $CAP_SPACE_12;
195
+
196
+ .button-item-url {
197
+ font-size: $FONT_SIZE_S;
198
+ color: $FONT_COLOR_03;
199
+ overflow: hidden;
200
+ text-overflow: ellipsis;
201
+ white-space: nowrap;
202
+ max-width: 12.857rem;
203
+ flex-shrink: 1;
204
+ margin-right: $CAP_SPACE_12;
205
+ }
206
+
207
+ .action-icons {
208
+ display: flex;
209
+ align-items: center;
210
+ gap: $CAP_SPACE_12;
211
+ flex-shrink: 0;
212
+
213
+ .action-icon {
214
+ font-size: $FONT_SIZE_L;
215
+ color: $FONT_COLOR_01;
216
+ cursor: pointer;
217
+ transition: color 0.2s ease;
218
+
219
+ &:hover {
220
+ color: map-get($CAP_SECONDARY, base);
221
+ }
222
+
223
+ &.delete-icon:hover {
224
+ color: $CAP_RED;
225
+ }
226
+ }
227
+ }
228
+ }
229
+
230
+ &.disabled {
231
+ opacity: 0.6;
232
+ cursor: not-allowed;
233
+ pointer-events: none;
234
+ }
235
+ }
236
+
237
+ .creatives-webpush-buttons-section {
238
+ margin-top: $CAP_SPACE_20;
239
+ margin-bottom: $CAP_SPACE_24;
240
+
241
+ .webpush-buttons-section-heading {
242
+ margin-bottom: $CAP_SPACE_16;
243
+ font-weight: 600;
244
+ }
245
+ }
246
+