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;