UNPKG

box-ui-elements-mlh

Version:
316 lines (297 loc) 14.4 kB
/** * @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;