Skip to content

Commit 2b29352

Browse files
committed
feat(Content-sharing): Add event callbacks to Content Sharing
1 parent 10c815c commit 2b29352

File tree

2 files changed

+80
-10
lines changed

2 files changed

+80
-10
lines changed

src/elements/content-sharing/ContentSharingV2.tsx

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import isEmpty from 'lodash/isEmpty';
12
import * as React from 'react';
23
import { useIntl } from 'react-intl';
3-
import isEmpty from 'lodash/isEmpty';
44

55
import { useNotification } from '@box/blueprint-web';
66
import { UnifiedShareModal } from '@box/unified-share-modal';
@@ -38,11 +38,27 @@ export interface ContentSharingV2Props {
3838
itemId: string;
3939
/** itemType - "file" or "folder" */
4040
itemType: ItemType;
41+
/** onClose - Callback when the modal is closed by user action */
42+
onClose?: () => void;
43+
/** onError - Callback when item data fails to load, preventing USM from opening */
44+
onError?: (error: Error) => void;
45+
/** onLoad - Callback when item data loads successfully, use to hide loading indicator */
46+
onLoad?: () => void;
4147
/** variant - "desktop" or "modal" variant of the Unified Share Modal */
4248
variant?: VariantType;
4349
}
4450

45-
function ContentSharingV2({ api, children, config: usmConfig, itemId, itemType, variant }: ContentSharingV2Props) {
51+
function ContentSharingV2({
52+
api,
53+
children,
54+
config: usmConfig,
55+
itemId,
56+
itemType,
57+
onClose,
58+
onError,
59+
onLoad,
60+
variant,
61+
}: ContentSharingV2Props) {
4662
const [avatarUrlMap, setAvatarUrlMap] = React.useState<AvatarURLMap | null>(null);
4763
const [item, setItem] = React.useState<Item | null>(null);
4864
const [hasError, setHasError] = React.useState<boolean>(false);
@@ -52,7 +68,7 @@ function ContentSharingV2({ api, children, config: usmConfig, itemId, itemType,
5268
const [collaborationRoles, setCollaborationRoles] = React.useState<CollaborationRole[] | null>(null);
5369
const [collaborators, setCollaborators] = React.useState<Collaborator[] | null>(null);
5470
const [collaboratorsData, setCollaboratorsData] = React.useState<Collaborations | null>(null);
55-
const [owner, setOwner] = React.useState({ id: '', email: '', name: '' });
71+
const [owner, setOwner] = React.useState({ email: '', id: '', name: '' });
5672

5773
const { formatMessage } = useIntl();
5874
const { addNotification } = useNotification();
@@ -86,7 +102,7 @@ function ContentSharingV2({ api, children, config: usmConfig, itemId, itemType,
86102
setSharedLink(sharedLinkFromApi);
87103
setSharingServiceProps(sharingServicePropsFromApi);
88104
setCollaborationRoles(collaborationRolesFromApi);
89-
setOwner({ id: ownedBy.id, email: ownedBy.login, name: ownedBy.name });
105+
setOwner({ email: ownedBy.login, id: ownedBy.id, name: ownedBy.name });
90106
}, []);
91107

92108
// Handle initial data retrieval errors
@@ -114,12 +130,12 @@ function ContentSharingV2({ api, children, config: usmConfig, itemId, itemType,
114130
addNotification({
115131
closeButtonAriaLabel: formatMessage(messages.noticeCloseLabel),
116132
sensitivity: 'foreground' as const,
133+
styledText: formatMessage(errorMessage),
117134
typeIconAriaLabel: formatMessage(messages.errorNoticeIcon),
118135
variant: 'error',
119-
styledText: formatMessage(errorMessage),
120136
});
121137
},
122-
[hasError, addNotification, formatMessage],
138+
[addNotification, formatMessage, hasError],
123139
);
124140

125141
// Reset state if the API has changed
@@ -142,21 +158,23 @@ function ContentSharingV2({ api, children, config: usmConfig, itemId, itemType,
142158
try {
143159
const itemData = await fetchItem({ api, itemId, itemType });
144160
handleGetItemSuccess(itemData);
161+
onLoad?.();
145162
} catch (error) {
163+
onError?.(error);
146164
getError(error);
147165
}
148166
})();
149-
}, [api, item, itemId, itemType, sharedLink, handleGetItemSuccess, getError]);
167+
}, [api, item, itemId, itemType, sharedLink, getError, handleGetItemSuccess, onError, onLoad]);
150168

151169
// Get current user
152170
React.useEffect(() => {
153171
if (!api || isEmpty(api) || !item || currentUser) return;
154172

155173
const getUserSuccess = userData => {
156-
const { id, enterprise, hostname } = userData;
174+
const { enterprise, hostname, id } = userData;
157175
setCurrentUser({
158-
id,
159176
enterprise: { name: enterprise ? enterprise.name : '' },
177+
id,
160178
});
161179
setSharingServiceProps(prevSharingServiceProps => ({
162180
...prevSharingServiceProps,
@@ -172,7 +190,7 @@ function ContentSharingV2({ api, children, config: usmConfig, itemId, itemType,
172190
getError(error);
173191
}
174192
})();
175-
}, [api, currentUser, item, itemId, itemType, sharedLink, getError]);
193+
}, [api, currentUser, item, itemId, itemType, sharedLink, getError, onError]);
176194

177195
// Get collaborators
178196
React.useEffect(() => {
@@ -222,6 +240,12 @@ function ContentSharingV2({ api, children, config: usmConfig, itemId, itemType,
222240

223241
const config = React.useMemo(() => ({ sharedLinkEmail: false, ...usmConfig }), [usmConfig]);
224242

243+
const handleOpenChange = (open: boolean) => {
244+
if (!open) {
245+
onClose?.();
246+
}
247+
};
248+
225249
return (
226250
item && (
227251
<UnifiedShareModal
@@ -231,6 +255,7 @@ function ContentSharingV2({ api, children, config: usmConfig, itemId, itemType,
231255
contactService={contactService}
232256
currentUser={currentUser}
233257
item={item}
258+
onOpenChange={handleOpenChange}
234259
sharedLink={sharedLink}
235260
sharingService={sharingService}
236261
variant={variant}

src/elements/content-sharing/__tests__/ContentSharingV2.test.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,4 +253,49 @@ describe('elements/content-sharing/ContentSharingV2', () => {
253253
});
254254
});
255255
});
256+
257+
describe('callback props', () => {
258+
const onLoad = jest.fn();
259+
const onError = jest.fn();
260+
const onClose = jest.fn();
261+
262+
const error = { status: 400 };
263+
const errorApi = {
264+
...defaultApiMock,
265+
getFileAPI: jest.fn().mockReturnValue({
266+
getFile: jest.fn().mockImplementation((id, successFn, errorFn) => {
267+
errorFn(error);
268+
}),
269+
}),
270+
};
271+
272+
test('should call onLoad and not onError when item data loads successfully', async () => {
273+
renderComponent({ onLoad });
274+
275+
await waitFor(() => {
276+
expect(onLoad).toHaveBeenCalled();
277+
});
278+
279+
expect(onError).not.toHaveBeenCalled();
280+
});
281+
282+
test('should call onError and not onLoad when item data fails to load', async () => {
283+
renderComponent({ api: errorApi, onError });
284+
285+
await waitFor(() => {
286+
expect(onError).toHaveBeenCalledWith(error);
287+
});
288+
289+
expect(onLoad).not.toHaveBeenCalled();
290+
});
291+
292+
test('should call onClose when modal is closed', async () => {
293+
renderComponent({ onClose });
294+
expect(await screen.findByRole('heading', { name: 'Share ‘Box Development Guide.pdf’' })).toBeVisible();
295+
296+
const closeButton = screen.getByRole('button', { name: 'Close' });
297+
closeButton.click();
298+
expect(onClose).toHaveBeenCalled();
299+
});
300+
});
256301
});

0 commit comments

Comments
 (0)