box-ui-elements-mlh
Version:
316 lines (297 loc) • 14.4 kB
JavaScript
/**
* @flow
* @file SharingModal
* @description This is the second-level component for the ContentSharing Element. It receives an API instance
* from its parent component, ContentSharing, and then instantiates the UnifiedShareModal with API data.
* @author Box
*/
import * as React from 'react';
import isEmpty from 'lodash/isEmpty';
import noop from 'lodash/noop';
import { FormattedMessage } from 'react-intl';
import type { $AxiosError } from 'axios';
import API from '../../api';
import Internationalize from '../common/Internationalize';
import NotificationsWrapper from '../../components/notification/NotificationsWrapper';
import Notification from '../../components/notification/Notification';
import { DURATION_SHORT, TYPE_ERROR } from '../../components/notification/constants';
import LoadingIndicator from '../../components/loading-indicator/LoadingIndicator';
import UnifiedShareModal from '../../features/unified-share-modal';
import SharedLinkSettingsModal from '../../features/shared-link-settings-modal';
import SharingNotification from './SharingNotification';
import {
convertItemResponse,
convertUserContactsByEmailResponse,
convertUserResponse,
} from '../../features/unified-share-modal/utils/convertData';
import useContactsByEmail from './hooks/useContactsByEmail';
import { FIELD_ENTERPRISE, FIELD_HOSTNAME, TYPE_FILE, TYPE_FOLDER } from '../../constants';
import { CONTENT_SHARING_ERRORS, CONTENT_SHARING_ITEM_FIELDS, CONTENT_SHARING_VIEWS } from './constants';
import { INVITEE_PERMISSIONS_FOLDER, INVITEE_PERMISSIONS_FILE } from '../../features/unified-share-modal/constants';
import contentSharingMessages from './messages';
import type { ErrorResponseData } from '../../common/types/api';
import type { ItemType, StringMap } from '../../common/types/core';
import type {
collaboratorsListType,
item as itemFlowType,
USMConfig,
} from '../../features/unified-share-modal/flowTypes';
import type {
ContentSharingItemAPIResponse,
ContentSharingSharedLinkType,
GetContactsFnType,
GetContactsByEmailFnType,
SendInvitesFnType,
SharedLinkUpdateLevelFnType,
SharedLinkUpdateSettingsFnType,
} from './types';
type SharingModalProps = {
api: API,
config?: USMConfig,
displayInModal: boolean,
isVisible: boolean,
itemID: string,
itemType: ItemType,
language: string,
messages?: StringMap,
setIsVisible: (arg: boolean) => void,
uuid?: string,
};
function SharingModal({
api,
config,
displayInModal,
isVisible,
itemID,
itemType,
language,
messages,
setIsVisible,
uuid,
}: SharingModalProps) {
const [item, setItem] = React.useState<itemFlowType | null>(null);
const [sharedLink, setSharedLink] = React.useState<ContentSharingSharedLinkType | null>(null);
const [currentUserEnterpriseName, setCurrentUserEnterpriseName] = React.useState<string | null>(null);
const [currentUserID, setCurrentUserID] = React.useState<string | null>(null);
const [initialDataErrorMessage, setInitialDataErrorMessage] = React.useState<Object | null>(null);
const [isInitialDataErrorVisible, setIsInitialDataErrorVisible] = React.useState<boolean>(false);
const [collaboratorsList, setCollaboratorsList] = React.useState<collaboratorsListType | null>(null);
const [onAddLink, setOnAddLink] = React.useState<null | SharedLinkUpdateLevelFnType>(null);
const [onRemoveLink, setOnRemoveLink] = React.useState<null | SharedLinkUpdateLevelFnType>(null);
const [
changeSharedLinkAccessLevel,
setChangeSharedLinkAccessLevel,
] = React.useState<null | SharedLinkUpdateLevelFnType>(null);
const [
changeSharedLinkPermissionLevel,
setChangeSharedLinkPermissionLevel,
] = React.useState<null | SharedLinkUpdateLevelFnType>(null);
const [onSubmitSettings, setOnSubmitSettings] = React.useState<null | SharedLinkUpdateSettingsFnType>(null);
const [currentView, setCurrentView] = React.useState<string>(CONTENT_SHARING_VIEWS.UNIFIED_SHARE_MODAL);
const [getContacts, setGetContacts] = React.useState<null | GetContactsFnType>(null);
const [getContactsByEmail, setGetContactsByEmail] = React.useState<null | GetContactsByEmailFnType>(null);
const [sendInvites, setSendInvites] = React.useState<null | SendInvitesFnType>(null);
const [isLoading, setIsLoading] = React.useState<boolean>(true);
// Handle successful GET requests to /files or /folders
const handleGetItemSuccess = React.useCallback((itemData: ContentSharingItemAPIResponse) => {
const { item: itemFromAPI, sharedLink: sharedLinkFromAPI } = convertItemResponse(itemData);
setItem(itemFromAPI);
setSharedLink(sharedLinkFromAPI);
setIsLoading(false);
}, []);
// Handle initial data retrieval errors
const getError = React.useCallback(
(error: $AxiosError<Object> | ErrorResponseData) => {
if (isInitialDataErrorVisible) return; // display only one component-level notification at a time
setIsInitialDataErrorVisible(true);
setIsLoading(false);
let errorObject;
if (error.status) {
errorObject = contentSharingMessages[CONTENT_SHARING_ERRORS[error.status]];
} else if (error.response && error.response.status) {
errorObject = contentSharingMessages[CONTENT_SHARING_ERRORS[error.response.status]];
} else {
errorObject = contentSharingMessages.loadingError;
}
setInitialDataErrorMessage(errorObject);
},
[isInitialDataErrorVisible],
);
// Reset state if the API has changed
React.useEffect(() => {
setChangeSharedLinkAccessLevel(null);
setChangeSharedLinkPermissionLevel(null);
setCollaboratorsList(null);
setInitialDataErrorMessage(null);
setCurrentUserID(null);
setCurrentUserEnterpriseName(null);
setIsInitialDataErrorVisible(false);
setIsLoading(true);
setItem(null);
setOnAddLink(null);
setOnRemoveLink(null);
setSharedLink(null);
}, [api]);
// Refresh error state if the uuid has changed
React.useEffect(() => {
setInitialDataErrorMessage(null);
setIsInitialDataErrorVisible(false);
}, [uuid]);
// Get initial data for the item
React.useEffect(() => {
const getItem = () => {
if (itemType === TYPE_FILE) {
api.getFileAPI().getFile(itemID, handleGetItemSuccess, getError, {
fields: CONTENT_SHARING_ITEM_FIELDS,
});
} else if (itemType === TYPE_FOLDER) {
api.getFolderAPI().getFolderFields(itemID, handleGetItemSuccess, getError, {
fields: CONTENT_SHARING_ITEM_FIELDS,
});
}
};
if (api && !isEmpty(api) && !initialDataErrorMessage && isVisible && !item && !sharedLink) {
getItem();
}
}, [api, initialDataErrorMessage, getError, handleGetItemSuccess, isVisible, item, itemID, itemType, sharedLink]);
// Get initial data for the user
React.useEffect(() => {
const getUserSuccess = userData => {
const { id, userEnterpriseData } = convertUserResponse(userData);
setCurrentUserID(id);
setCurrentUserEnterpriseName(userEnterpriseData.enterpriseName || null);
setSharedLink(prevSharedLink => ({ ...prevSharedLink, ...userEnterpriseData }));
setInitialDataErrorMessage(null);
setIsLoading(false);
};
const getUserData = () => {
api.getUsersAPI(false).getUser(itemID, getUserSuccess, getError, {
params: {
fields: [FIELD_ENTERPRISE, FIELD_HOSTNAME].toString(),
},
});
};
if (api && !isEmpty(api) && !initialDataErrorMessage && item && sharedLink && !currentUserID) {
getUserData();
}
}, [getError, item, itemID, itemType, sharedLink, currentUserID, api, initialDataErrorMessage]);
// Set the getContactsByEmail function. This call is not associated with a banner notification,
// which is why it exists at this level and not in SharingNotification
const getContactsByEmailFn: GetContactsByEmailFnType | null = useContactsByEmail(api, itemID, {
transformUsers: data => convertUserContactsByEmailResponse(data),
});
if (getContactsByEmailFn && !getContactsByEmail) {
setGetContactsByEmail((): GetContactsByEmailFnType => getContactsByEmailFn);
}
// Display a notification if there is an error in retrieving initial data
if (initialDataErrorMessage) {
return isInitialDataErrorVisible ? (
<Internationalize language={language} messages={messages}>
<NotificationsWrapper>
<Notification
onClose={() => setIsInitialDataErrorVisible(false)}
type={TYPE_ERROR}
duration={DURATION_SHORT}
>
<span>
<FormattedMessage {...initialDataErrorMessage} />
</span>
</Notification>
</NotificationsWrapper>
</Internationalize>
) : null;
}
// Ensure that all necessary data has been received before rendering child components.
// If the USM is visible, show the LoadingIndicator; otherwise, show nothing.
// "serverURL" is added to sharedLink after the call to the Users API, so it needs to be checked separately.
if (!item || !sharedLink || !currentUserID || !sharedLink.serverURL) {
return isVisible ? <LoadingIndicator /> : null;
}
const { ownerEmail, ownerID, permissions } = item;
const {
accessLevel = '',
canChangeExpiration = false,
expirationTimestamp,
isDownloadAvailable = false,
serverURL,
} = sharedLink;
return (
<Internationalize language={language} messages={messages}>
<>
<SharingNotification
accessLevel={accessLevel}
api={api}
closeComponent={displayInModal ? () => setIsVisible(false) : noop}
closeSettings={() => setCurrentView(CONTENT_SHARING_VIEWS.UNIFIED_SHARE_MODAL)}
collaboratorsList={collaboratorsList}
currentUserID={currentUserID}
getContacts={getContacts}
isDownloadAvailable={isDownloadAvailable}
itemID={itemID}
itemType={itemType}
onSubmitSettings={onSubmitSettings}
ownerEmail={ownerEmail}
ownerID={ownerID}
permissions={permissions}
sendInvites={sendInvites}
serverURL={serverURL}
setChangeSharedLinkAccessLevel={setChangeSharedLinkAccessLevel}
setChangeSharedLinkPermissionLevel={setChangeSharedLinkPermissionLevel}
setGetContacts={setGetContacts}
setCollaboratorsList={setCollaboratorsList}
setIsLoading={setIsLoading}
setItem={setItem}
setOnAddLink={setOnAddLink}
setOnRemoveLink={setOnRemoveLink}
setOnSubmitSettings={setOnSubmitSettings}
setSendInvites={setSendInvites}
setSharedLink={setSharedLink}
/>
{isVisible && currentView === CONTENT_SHARING_VIEWS.SHARED_LINK_SETTINGS && (
<SharedLinkSettingsModal
isDirectLinkUnavailableDueToDownloadSettings={false}
isDirectLinkUnavailableDueToAccessPolicy={false}
isDirectLinkUnavailableDueToMaliciousContent={false}
isOpen={isVisible}
item={item}
onRequestClose={() => setCurrentView(CONTENT_SHARING_VIEWS.UNIFIED_SHARE_MODAL)}
onSubmit={onSubmitSettings}
submitting={isLoading}
{...sharedLink}
canChangeExpiration={canChangeExpiration && !!currentUserEnterpriseName}
/>
)}
{isVisible && currentView === CONTENT_SHARING_VIEWS.UNIFIED_SHARE_MODAL && (
<UnifiedShareModal
canInvite={sharedLink.canInvite}
config={config}
changeSharedLinkAccessLevel={changeSharedLinkAccessLevel}
changeSharedLinkPermissionLevel={changeSharedLinkPermissionLevel}
collaboratorsList={collaboratorsList}
currentUserID={currentUserID}
displayInModal={displayInModal}
getCollaboratorContacts={getContacts}
getContactsByEmail={getContactsByEmail}
initialDataReceived
inviteePermissions={
itemType === TYPE_FOLDER ? INVITEE_PERMISSIONS_FOLDER : INVITEE_PERMISSIONS_FILE
}
isOpen={isVisible}
item={item}
onAddLink={onAddLink}
onRequestClose={displayInModal ? () => setIsVisible(false) : noop}
onRemoveLink={onRemoveLink}
onSettingsClick={() => setCurrentView(CONTENT_SHARING_VIEWS.SHARED_LINK_SETTINGS)}
sendInvites={sendInvites}
sharedLink={{
...sharedLink,
expirationTimestamp: expirationTimestamp ? expirationTimestamp / 1000 : null,
}} // the USM expects this value in seconds, while the SLSM expects this value in milliseconds
submitting={isLoading}
/>
)}
</>
</Internationalize>
);
}
export default SharingModal;