Skip to content

Commit b412e29

Browse files
authored
feat: add freemium upsell changes (#2739)
feat: add freemium upsell changes
1 parent d11b23a commit b412e29

10 files changed

Lines changed: 156 additions & 25 deletions

File tree

i18n/en-US.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,10 @@ be.uploadsCancelButtonTooltip = Cancel this upload
666666
be.uploadsDefaultErrorMessage = Something went wrong with the upload. Please try again.
667667
# Error message shown when file size exceeds the limit
668668
be.uploadsFileSizeLimitExceededErrorMessage = File size exceeds the folder owner’s file size limit
669+
# Error message shown when file size exceeds the limit
670+
be.uploadsFileSizeLimitExceededErrorMessageForUpgradeCta = This file exceeds your plan’s upload limit. Upgrade now to store larger files.
671+
# Upgrade message shown when file size exceeds the limit
672+
be.uploadsFileSizeLimitExceededUpgradeMessageForUpgradeCta = Upgrade
669673
# Error message shown when attempting to upload a file which name already exists
670674
be.uploadsItemNameInUseErrorMessage = A file with this name already exists.
671675
# Text shown when uploads are completed

src/elements/common/messages.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,16 @@ const messages = defineMessages({
687687
description: 'Error message shown when file size exceeds the limit',
688688
defaultMessage: 'File size exceeds the folder owner’s file size limit',
689689
},
690+
uploadsFileSizeLimitExceededErrorMessageForUpgradeCta: {
691+
id: 'be.uploadsFileSizeLimitExceededErrorMessageForUpgradeCta',
692+
description: 'Error message shown when file size exceeds the limit',
693+
defaultMessage: 'This file exceeds your plan’s upload limit. Upgrade now to store larger files.',
694+
},
695+
uploadsFileSizeLimitExceededUpgradeMessageForUpgradeCta: {
696+
id: 'be.uploadsFileSizeLimitExceededUpgradeMessageForUpgradeCta',
697+
description: 'Upgrade message shown when file size exceeds the limit',
698+
defaultMessage: 'Upgrade',
699+
},
690700
uploadsStorageLimitErrorMessage: {
691701
id: 'be.uploadsStorageLimitErrorMessage',
692702
description: 'Error message shown when account storage limit has been reached',

src/elements/content-uploader/ContentUploader.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ type Props = {
9090
onMinimize?: Function,
9191
onProgress: Function,
9292
onResume: Function,
93+
onUpgradeCTAClick?: Function,
9394
onUpload: Function,
9495
overwrite: boolean,
9596
requestInterceptor?: Function,
@@ -1203,17 +1204,18 @@ class ContentUploader extends Component<Props, State> {
12031204
*/
12041205
render() {
12051206
const {
1207+
className,
1208+
fileLimit,
1209+
isDraggingItemsToUploadsManager = false,
1210+
isFolderUploadEnabled,
1211+
isResumableUploadsEnabled,
1212+
isTouch,
12061213
language,
1214+
measureRef,
12071215
messages,
12081216
onClose,
1209-
className,
1210-
measureRef,
1211-
isTouch,
1212-
fileLimit,
1217+
onUpgradeCTAClick,
12131218
useUploadsManager,
1214-
isResumableUploadsEnabled,
1215-
isFolderUploadEnabled,
1216-
isDraggingItemsToUploadsManager = false,
12171219
}: Props = this.props;
12181220
const { view, items, errorCode, isUploadsManagerExpanded }: State = this.state;
12191221
const isEmpty = items.length === 0;
@@ -1240,6 +1242,7 @@ class ContentUploader extends Component<Props, State> {
12401242
items={items}
12411243
onItemActionClick={this.onClick}
12421244
onRemoveActionClick={this.removeFileFromUploadQueue}
1245+
onUpgradeCTAClick={onUpgradeCTAClick}
12431246
onUploadsManagerActionClick={this.clickAllWithStatus}
12441247
toggleUploadsManager={this.toggleUploadsManager}
12451248
view={view}

src/elements/content-uploader/ItemAction.js

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,52 @@
44
*/
55

66
import React from 'react';
7-
import { injectIntl } from 'react-intl';
7+
import { FormattedMessage, injectIntl } from 'react-intl';
88
import type { InjectIntlProvidedProps } from 'react-intl';
99
import IconCheck from '../../icons/general/IconCheck';
1010
import IconClose from '../../icons/general/IconClose';
1111
import IconInProgress from './IconInProgress';
1212
import IconRetry from '../../icons/general/IconRetry';
1313
import LoadingIndicator from '../../components/loading-indicator';
1414
import PlainButton from '../../components/plain-button/PlainButton';
15+
import PrimaryButton from '../../components/primary-button/PrimaryButton';
1516
import Tooltip from '../../components/tooltip';
1617
import messages from '../common/messages';
17-
import { STATUS_PENDING, STATUS_IN_PROGRESS, STATUS_STAGED, STATUS_COMPLETE, STATUS_ERROR } from '../../constants';
18+
import {
19+
ERROR_CODE_UPLOAD_FILE_SIZE_LIMIT_EXCEEDED,
20+
STATUS_PENDING,
21+
STATUS_IN_PROGRESS,
22+
STATUS_STAGED,
23+
STATUS_COMPLETE,
24+
STATUS_ERROR,
25+
} from '../../constants';
1826
import type { UploadStatus } from '../../common/types/upload';
1927

2028
import './ItemAction.scss';
2129

2230
const ICON_CHECK_COLOR = '#26C281';
2331

2432
type Props = {
33+
error?: Object,
2534
isFolder?: boolean,
2635
isResumableUploadsEnabled: boolean,
2736
onClick: Function,
37+
onUpgradeCTAClick?: Function,
2838
status: UploadStatus,
2939
} & InjectIntlProvidedProps;
3040

31-
const ItemAction = ({ status, onClick, intl, isResumableUploadsEnabled, isFolder = false }: Props) => {
41+
const ItemAction = ({
42+
error = {},
43+
intl,
44+
isFolder = false,
45+
isResumableUploadsEnabled,
46+
onClick,
47+
onUpgradeCTAClick,
48+
status,
49+
}: Props) => {
3250
let icon = <IconClose />;
3351
let tooltip;
52+
const { code } = error;
3453

3554
if (isFolder && status !== STATUS_PENDING) {
3655
return null;
@@ -66,6 +85,18 @@ const ItemAction = ({ status, onClick, intl, isResumableUploadsEnabled, isFolder
6685
break;
6786
}
6887

88+
if (status === STATUS_ERROR && code === ERROR_CODE_UPLOAD_FILE_SIZE_LIMIT_EXCEEDED && !!onUpgradeCTAClick) {
89+
return (
90+
<PrimaryButton
91+
onClick={onUpgradeCTAClick}
92+
data-resin-target="large_version_error_inline_upgrade_cta"
93+
type="button"
94+
>
95+
<FormattedMessage {...messages.uploadsFileSizeLimitExceededUpgradeMessageForUpgradeCta} />
96+
</PrimaryButton>
97+
);
98+
}
99+
69100
return (
70101
<div className="bcu-item-action">
71102
{tooltip ? (

src/elements/content-uploader/ItemList.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,21 @@ type Props = {
2020
items: UploadItem[],
2121
onClick: Function,
2222
onRemoveClick?: (item: UploadItem) => void,
23+
onUpgradeCTAClick?: Function,
2324
};
2425

25-
const ItemList = ({ isResumableUploadsEnabled = false, items, onClick, onRemoveClick = noop }: Props) => (
26+
const ItemList = ({
27+
isResumableUploadsEnabled = false,
28+
items,
29+
onClick,
30+
onRemoveClick = noop,
31+
onUpgradeCTAClick,
32+
}: Props) => (
2633
<AutoSizer>
2734
{({ width, height }) => {
2835
const nameCell = nameCellRenderer(isResumableUploadsEnabled);
29-
const progressCell = progressCellRenderer();
30-
const actionCell = actionCellRenderer(isResumableUploadsEnabled, onClick);
36+
const progressCell = progressCellRenderer(!!onUpgradeCTAClick);
37+
const actionCell = actionCellRenderer(isResumableUploadsEnabled, onClick, onUpgradeCTAClick);
3138
const removeCell = removeCellRenderer(onRemoveClick);
3239

3340
return (
@@ -56,7 +63,7 @@ const ItemList = ({ isResumableUploadsEnabled = false, items, onClick, onRemoveC
5663
cellRenderer={actionCell}
5764
dataKey="status"
5865
flexShrink={0}
59-
width={25}
66+
width={onUpgradeCTAClick ? 100 : 25}
6067
/>
6168
{isResumableUploadsEnabled && (
6269
<Column

src/elements/content-uploader/UploadsManager.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,24 @@ type Props = {
2121
items: UploadItem[],
2222
onItemActionClick: Function,
2323
onRemoveActionClick: (item: UploadItem) => void,
24+
onUpgradeCTAClick?: Function,
2425
onUploadsManagerActionClick: Function,
2526
toggleUploadsManager: Function,
2627
view: View,
2728
};
2829

2930
const UploadsManager = ({
31+
isExpanded,
32+
isVisible,
33+
isResumableUploadsEnabled,
34+
isDragging,
3035
items,
31-
view,
3236
onItemActionClick,
3337
onRemoveActionClick,
38+
onUpgradeCTAClick,
3439
onUploadsManagerActionClick,
3540
toggleUploadsManager,
36-
isExpanded,
37-
isVisible,
38-
isResumableUploadsEnabled,
39-
isDragging,
41+
view,
4042
}: Props) => {
4143
/**
4244
* Keydown handler for progress bar
@@ -98,6 +100,7 @@ const UploadsManager = ({
98100
items={items}
99101
onClick={onItemActionClick}
100102
onRemoveClick={onRemoveActionClick}
103+
onUpgradeCTAClick={onUpgradeCTAClick}
101104
view={view}
102105
/>
103106
</div>

src/elements/content-uploader/__tests__/ItemAction.test.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ import React from 'react';
22
import noop from 'lodash/noop';
33
import { shallow } from 'enzyme';
44
import { ItemActionForTesting as ItemAction } from '../ItemAction';
5-
import { STATUS_PENDING, STATUS_IN_PROGRESS, STATUS_COMPLETE, STATUS_STAGED, STATUS_ERROR } from '../../../constants';
5+
import {
6+
ERROR_CODE_UPLOAD_FILE_SIZE_LIMIT_EXCEEDED,
7+
STATUS_PENDING,
8+
STATUS_IN_PROGRESS,
9+
STATUS_COMPLETE,
10+
STATUS_STAGED,
11+
STATUS_ERROR,
12+
} from '../../../constants';
613

714
describe('elements/content-uploader/ItemAction', () => {
815
const getWrapper = props =>
@@ -58,4 +65,15 @@ describe('elements/content-uploader/ItemAction', () => {
5865

5966
expect(wrapper).toMatchSnapshot();
6067
});
68+
69+
test('should render PrimaryButton with STATUS_ERROR and upload file size exceeded', () => {
70+
const wrapper = getWrapper({
71+
status: STATUS_ERROR,
72+
error: { code: ERROR_CODE_UPLOAD_FILE_SIZE_LIMIT_EXCEEDED },
73+
onUpgradeCTAClick: () => {},
74+
});
75+
76+
expect(wrapper.exists('PrimaryButton')).toBe(true);
77+
expect(wrapper.exists('PlainButton')).toBe(false);
78+
});
6179
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React from 'react';
2+
import { mount } from 'enzyme';
3+
4+
import { ERROR_CODE_UPLOAD_FILE_SIZE_LIMIT_EXCEEDED, STATUS_COMPLETE, STATUS_ERROR } from '../../../constants';
5+
import ItemList from '../ItemList';
6+
7+
jest.mock('@box/react-virtualized/dist/es/AutoSizer', () => ({ children }) => children({ height: 600, width: 600 }));
8+
9+
describe('elements/content-uploader/ItemList', () => {
10+
const renderComponent = props => mount(<ItemList items={[]} onClick={() => {}} {...props} />);
11+
12+
describe('render()', () => {
13+
test('should render default component', () => {
14+
const wrapper = renderComponent();
15+
16+
expect(wrapper.find('Table').length).toBe(1);
17+
expect(wrapper.find('Table.bcu-item-list').length).toBe(1);
18+
});
19+
20+
test('should render component with correct number of items', () => {
21+
const items = [
22+
{ id: '1', name: 'item1', status: STATUS_COMPLETE },
23+
{ id: '2', name: 'item2', status: STATUS_COMPLETE },
24+
{ id: '3', name: 'item3', status: STATUS_COMPLETE },
25+
];
26+
const wrapper = renderComponent({ items });
27+
expect(wrapper.find('div.bcu-item-row').length).toBe(3);
28+
const actionColumnStyle = wrapper
29+
.find('.bcu-item-list-action-column')
30+
.first()
31+
.prop('style');
32+
expect(actionColumnStyle.flex).toEqual('0 0 25px');
33+
});
34+
35+
test('should render action column with correct width for upgrade cta', () => {
36+
const items = [
37+
{ id: '1', name: 'item1', status: STATUS_ERROR, code: ERROR_CODE_UPLOAD_FILE_SIZE_LIMIT_EXCEEDED },
38+
];
39+
const wrapper = renderComponent({ items, onUpgradeCTAClick: () => {} });
40+
expect(wrapper.find('div.bcu-item-row').length).toBe(1);
41+
const actionColumnStyle = wrapper.find('.bcu-item-list-action-column').prop('style');
42+
expect(actionColumnStyle.flex).toEqual('0 0 100px');
43+
});
44+
});
45+
});

src/elements/content-uploader/actionCellRenderer.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ type Props = {
1111
rowData: UploadItem,
1212
};
1313

14-
export default (isResumableUploadsEnabled: boolean, onClick: Function) => ({ rowData }: Props) => (
15-
<ItemAction {...rowData} isResumableUploadsEnabled={isResumableUploadsEnabled} onClick={() => onClick(rowData)} />
14+
export default (isResumableUploadsEnabled: boolean, onClick: Function, onUpgradeCTAClick?: Function) => ({
15+
rowData,
16+
}: Props) => (
17+
<ItemAction
18+
{...rowData}
19+
isResumableUploadsEnabled={isResumableUploadsEnabled}
20+
onClick={() => onClick(rowData)}
21+
onUpgradeCTAClick={onUpgradeCTAClick}
22+
/>
1623
);

src/elements/content-uploader/progressCellRenderer.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@ type Props = {
3535
* @param {string} [itemName]
3636
* @returns {FormattedMessage}
3737
*/
38-
const getErrorMessage = (errorCode: ?string, itemName: ?string) => {
38+
const getErrorMessage = (errorCode: ?string, itemName: ?string, shouldShowUpgradeCTAMessage?: boolean) => {
3939
switch (errorCode) {
4040
case ERROR_CODE_UPLOAD_CHILD_FOLDER_FAILED:
4141
return <FormattedMessage {...messages.uploadsOneOrMoreChildFoldersFailedToUploadMessage} />;
4242
case ERROR_CODE_UPLOAD_FILE_SIZE_LIMIT_EXCEEDED:
43+
if (shouldShowUpgradeCTAMessage) {
44+
return <FormattedMessage {...messages.uploadsFileSizeLimitExceededErrorMessageForUpgradeCta} />;
45+
}
4346
return <FormattedMessage {...messages.uploadsFileSizeLimitExceededErrorMessage} />;
4447
case ERROR_CODE_ITEM_NAME_IN_USE:
4548
return <FormattedMessage {...messages.uploadsItemNameInUseErrorMessage} />;
@@ -58,7 +61,7 @@ const getErrorMessage = (errorCode: ?string, itemName: ?string) => {
5861
}
5962
};
6063

61-
export default () => ({ rowData }: Props) => {
64+
export default (shouldShowUpgradeCTAMessage?: boolean) => ({ rowData }: Props) => {
6265
const { status, error = {}, name, isFolder, file } = rowData;
6366
const { code } = error;
6467

@@ -74,7 +77,7 @@ export default () => ({ rowData }: Props) => {
7477
if (Browser.isSafari() && code === ERROR_CODE_UPLOAD_BAD_DIGEST && file.name.indexOf('.zip') !== -1) {
7578
return getErrorMessage(ERROR_CODE_UPLOAD_FAILED_PACKAGE, file.name);
7679
}
77-
return getErrorMessage(code, name);
80+
return getErrorMessage(code, name, shouldShowUpgradeCTAMessage);
7881
default:
7982
return null;
8083
}

0 commit comments

Comments
 (0)