Skip to content

Commit 2af11b1

Browse files
stevegcodermergify[bot]
authored andcommitted
feat(unified-share-modal): add property to limit the number of contacts (#1304)
Add property to limit the number of contact pills that can be created at one time. Remove reset of error on contact input. This preserves the contact limit error.
1 parent 2cdfcc4 commit 2af11b1

File tree

5 files changed

+65
-28
lines changed

5 files changed

+65
-28
lines changed

i18n/en-US.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,6 +1170,8 @@ boxui.unifiedShare.collaboration.userCollabText = User
11701170
boxui.unifiedShare.collaboratorListTitle = People in '{itemName}'
11711171
# This string is displayed as tooltip on hovering over expire icon for collab
11721172
boxui.unifiedShare.collaborators.expirationTooltipClickableText = Access expires on {date}. Click for details.
1173+
# Error message when more than the maximum number of contacts is entered
1174+
boxui.unifiedShare.contactsExceedLimitError = Oops! The maximum number of collaborators that can be added at once is {maxContacts} collaborators. Please try again by splitting your invitations into batches.
11731175
# Text shown in share modal when there is at least one external collaborators
11741176
boxui.unifiedShare.contentSharedWithExternalCollaborators = This content will be shared with external collaborators.
11751177
# Text used in button label to describe permission level - co-owner

src/features/unified-share-modal/EmailForm.js

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type { SelectOptionProp } from '../../components/select-field/props';
2828
type Props = {
2929
cancelButtonProps?: Object,
3030
children?: React.Node,
31+
contactLimit?: number,
3132
contactsFieldAvatars?: React.Node,
3233
contactsFieldDisabledTooltip: React.Node,
3334
contactsFieldLabel: React.Node,
@@ -81,9 +82,12 @@ class EmailForm extends React.Component<Props, State> {
8182
} = React.createRef();
8283

8384
handleContactAdd = (contacts: Array<Contact>) => {
84-
const { onContactAdd, updateSelectedContacts } = this.props;
85+
const { selectedContacts, onContactAdd, updateSelectedContacts } = this.props;
8586

86-
updateSelectedContacts([...this.props.selectedContacts, ...contacts]);
87+
const updatedContacts = [...selectedContacts, ...contacts];
88+
updateSelectedContacts(updatedContacts);
89+
90+
this.validateContacts(updatedContacts);
8791

8892
if (onContactAdd) {
8993
onContactAdd(contacts);
@@ -96,16 +100,33 @@ class EmailForm extends React.Component<Props, State> {
96100
const removed = selectedContacts.splice(index, 1);
97101
updateSelectedContacts(selectedContacts);
98102

103+
this.validateContacts(selectedContacts);
104+
99105
if (onContactRemove) {
100106
onContactRemove(removed);
101107
}
102108
};
103109

110+
validateContacts = (selectedContacts: Array<Contact>) => {
111+
const { contactLimit, intl } = this.props;
112+
113+
let contactsFieldError = '';
114+
if (contactLimit !== undefined && selectedContacts.length > contactLimit) {
115+
contactsFieldError = intl.formatMessage(messages.contactsExceedLimitError, {
116+
maxContacts: contactLimit,
117+
});
118+
} else if (selectedContacts.length === 0) {
119+
contactsFieldError = intl.formatMessage(messages.enterAtLeastOneEmailError);
120+
}
121+
122+
this.setState({ contactsFieldError });
123+
124+
return contactsFieldError;
125+
};
126+
104127
handleContactInput = (value: string) => {
105128
const { onContactInput } = this.props;
106129

107-
// As user is typing, reset error
108-
this.setState({ contactsFieldError: '' });
109130
if (onContactInput) {
110131
onContactInput(value);
111132
}
@@ -142,23 +163,16 @@ class EmailForm extends React.Component<Props, State> {
142163
handleSubmit = (event: SyntheticEvent<HTMLFormElement>) => {
143164
event.preventDefault();
144165

145-
const {
146-
intl: { formatMessage },
147-
onSubmit,
148-
selectedContacts,
149-
} = this.props;
166+
const { onSubmit, selectedContacts } = this.props;
150167
const { message, contactsFieldError } = this.state;
151168

152169
if (contactsFieldError !== '') {
153170
// Block submission if there's a validation error
154171
return;
155172
}
156173

157-
if (selectedContacts.length === 0) {
158-
// Block submission if no pills are selected
159-
this.setState({
160-
contactsFieldError: formatMessage(messages.enterAtLeastOneEmailError),
161-
});
174+
const contactsError = this.validateContacts(selectedContacts);
175+
if (contactsError) {
162176
return;
163177
}
164178

@@ -304,7 +318,7 @@ class EmailForm extends React.Component<Props, State> {
304318
<FormattedMessage {...commonMessages.cancel} />
305319
</Button>
306320
<PrimaryButton
307-
isDisabled={submitting || selectedContacts.length === 0}
321+
isDisabled={submitting || selectedContacts.length === 0 || contactsFieldError} // Check selectedContacts.length === 0 for initial render when contactsFieldError is empty
308322
isLoading={submitting}
309323
type="submit"
310324
{...sendButtonProps}

src/features/unified-share-modal/UnifiedShareModal.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ type Props = {
5858
collaborationRestrictionWarning: React.Node,
5959
/** List of existing collaborators */
6060
collaboratorsList?: collaboratorsListType,
61+
/** Used to limit the number of contacts that can be added in the contacts field */
62+
contactLimit?: number,
6163
/** User ID of currently logged in user */
6264
currentUserID: string,
6365
/** Whether the modal should focus the shared link after the URL is resolved */
@@ -454,6 +456,7 @@ class UnifiedShareModal extends React.Component<Props, State> {
454456
const {
455457
canInvite,
456458
collaborationRestrictionWarning,
459+
contactLimit,
457460
getCollaboratorContacts,
458461
item,
459462
sendInvitesError,
@@ -519,6 +522,7 @@ class UnifiedShareModal extends React.Component<Props, State> {
519522
>
520523
<div className="invite-collaborator-container">
521524
<EmailForm
525+
contactLimit={contactLimit}
522526
contactsFieldAvatars={avatars}
523527
contactsFieldDisabledTooltip={contactsFieldDisabledTooltip}
524528
contactsFieldLabel={<FormattedMessage {...messages.inviteFieldLabel} />}

src/features/unified-share-modal/__tests__/EmailForm-test.js

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import CollaboratorAvatars from '../../collaborator-avatars/CollaboratorAvatars'
44
import commonMessages from '../../../common/messages';
55

66
import { EmailFormBase as EmailForm } from '../EmailForm';
7-
import messages from '../messages';
87

98
describe('features/unified-share-modal/EmailForm', () => {
109
const expectedContacts = [
@@ -66,6 +65,19 @@ describe('features/unified-share-modal/EmailForm', () => {
6665
expect(updateSelectedContacts).toHaveBeenCalledWith(contactsToAdd);
6766
expect(onContactAdd).toBeCalled();
6867
});
68+
69+
test('should set error when contact limit reached', () => {
70+
const wrapper = getWrapper({
71+
contactLimit: 1,
72+
intl: {
73+
formatMessage: jest.fn().mockReturnValue('contact limit reached'),
74+
},
75+
});
76+
77+
const contactsToAdd = [expectedContacts[1], expectedContacts[2]];
78+
wrapper.instance().handleContactAdd(contactsToAdd);
79+
expect(wrapper.state('contactsFieldError')).toBe('contact limit reached');
80+
});
6981
});
7082

7183
describe('handleContactRemove()', () => {
@@ -84,21 +96,17 @@ describe('features/unified-share-modal/EmailForm', () => {
8496
expect(onContactRemove).toBeCalled();
8597
expect(updateSelectedContacts).toHaveBeenCalledWith([]);
8698
});
87-
});
88-
89-
describe('handleContactInput()', () => {
90-
test('should reset the error', () => {
91-
const onContactInput = jest.fn();
92-
const wrapper = getWrapper({ onContactInput });
9399

94-
wrapper.setState({
95-
contactsFieldError: 'Error',
100+
test('should set error when contact limit reached', () => {
101+
const wrapper = getWrapper({
102+
contactLimit: 1,
103+
contactsFieldError: 'contact limit reached',
104+
selectedContacts: [expectedContacts[1], expectedContacts[2]],
96105
});
97106

98-
wrapper.instance().handleContactInput();
107+
wrapper.instance().handleContactRemove(expectedContacts[1], 0);
99108

100-
expect(wrapper.state('contactsFieldError')).toEqual('');
101-
expect(onContactInput).toBeCalled();
109+
expect(wrapper.state('contactsFieldError')).toBe('');
102110
});
103111
});
104112

@@ -160,12 +168,15 @@ describe('features/unified-share-modal/EmailForm', () => {
160168
const onSubmitSpy = jest.fn();
161169
const wrapper = getWrapper({
162170
sendInvites: onSubmitSpy,
171+
intl: {
172+
formatMessage: () => 'error',
173+
},
163174
});
164175
const event = { preventDefault: jest.fn() };
165176

166177
wrapper.instance().handleSubmit(event);
167178

168-
expect(intl.formatMessage).toHaveBeenCalledWith(messages.enterAtLeastOneEmailError);
179+
expect(wrapper.state('contactsFieldError')).toEqual('error');
169180
expect(onSubmitSpy).not.toHaveBeenCalled();
170181
});
171182

src/features/unified-share-modal/messages.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ const messages = defineMessages({
1717
description: 'Error message when user tries to send Shared Link as email without entering any recipients',
1818
id: 'boxui.unifiedShare.enterAtLeastOneEmail',
1919
},
20+
contactsExceedLimitError: {
21+
defaultMessage:
22+
'Oops! The maximum number of collaborators that can be added at once is {maxContacts} collaborators. Please try again by splitting your invitations into batches.',
23+
description: 'Error message when more than the maximum number of contacts is entered',
24+
id: 'boxui.unifiedShare.contactsExceedLimitError',
25+
},
2026
enterEmailAddressesCalloutText: {
2127
defaultMessage: 'Share this item with coworkers by entering their email addresses',
2228
description:

0 commit comments

Comments
 (0)