Skip to content

Commit 17f8ae9

Browse files
chore(js-ts): Convert SharingModal to TypeScript
Co-Authored-By: Joseph Gross <[email protected]>
1 parent 512c975 commit 17f8ae9

File tree

4 files changed

+554
-1
lines changed

4 files changed

+554
-1
lines changed

src/elements/content-sharing/SharingModal.js renamed to src/elements/content-sharing/SharingModal.js.flow

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/**
2-
* @flow
32
* @file SharingModal
43
* @description This is the second-level component for the ContentSharing Element. It receives an API instance
54
* from its parent component, ContentSharing, and then instantiates the UnifiedShareModal with API data.
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
/**
2+
* @file SharingModal
3+
* @description This is the second-level component for the ContentSharing Element. It receives an API instance
4+
* from its parent component, ContentSharing, and then instantiates the UnifiedShareModal with API data.
5+
* @author Box
6+
*/
7+
import * as React from 'react';
8+
import isEmpty from 'lodash/isEmpty';
9+
import noop from 'lodash/noop';
10+
import { FormattedMessage, type MessageDescriptor } from 'react-intl';
11+
import type { AxiosError } from 'axios';
12+
import API from '../../api';
13+
import Internationalize from '../common/Internationalize';
14+
import NotificationsWrapper from '../../components/notification/NotificationsWrapper';
15+
import Notification from '../../components/notification/Notification';
16+
import { DURATION_SHORT, TYPE_ERROR } from '../../components/notification/constants';
17+
import LoadingIndicator from '../../components/loading-indicator/LoadingIndicator';
18+
import UnifiedShareModal from '../../features/unified-share-modal';
19+
import SharedLinkSettingsModal from '../../features/shared-link-settings-modal';
20+
import SharingNotification from './SharingNotification';
21+
import {
22+
convertItemResponse,
23+
convertUserContactsByEmailResponse,
24+
convertUserResponse,
25+
} from '../../features/unified-share-modal/utils/convertData';
26+
import useContactsByEmail from './hooks/useContactsByEmail';
27+
import { FIELD_ENTERPRISE, FIELD_HOSTNAME, TYPE_FILE, TYPE_FOLDER } from '../../constants';
28+
import { CONTENT_SHARING_ERRORS, CONTENT_SHARING_ITEM_FIELDS, CONTENT_SHARING_VIEWS } from './constants';
29+
import { INVITEE_PERMISSIONS_FOLDER, INVITEE_PERMISSIONS_FILE } from '../../features/unified-share-modal/constants';
30+
import contentSharingMessages from './messages';
31+
import type { ErrorResponseData } from '../../common/types/api';
32+
import type { ItemType, StringMap, User } from '../../common/types/core';
33+
import type { Item as itemFlowType, USMConfig, CollaboratorListType } from '../../features/unified-share-modal/types';
34+
import type {
35+
ContentSharingItemAPIResponse,
36+
ContentSharingSharedLinkType,
37+
GetContactsFnType,
38+
GetContactsByEmailFnType,
39+
SendInvitesFnType,
40+
SharedLinkUpdateLevelFnType,
41+
SharedLinkUpdateSettingsFnType,
42+
} from './types';
43+
44+
type SharingModalProps = {
45+
api: API;
46+
config?: USMConfig;
47+
displayInModal: boolean;
48+
isVisible: boolean;
49+
itemID: string;
50+
itemType: ItemType;
51+
language: string;
52+
messages?: StringMap;
53+
setIsVisible: (arg: boolean) => void;
54+
uuid?: string;
55+
};
56+
57+
function SharingModal({
58+
api,
59+
config,
60+
displayInModal,
61+
isVisible,
62+
itemID,
63+
itemType,
64+
language,
65+
messages,
66+
setIsVisible,
67+
uuid,
68+
}: SharingModalProps) {
69+
const [item, setItem] = React.useState<itemFlowType | null>(null);
70+
const [sharedLink, setSharedLink] = React.useState<ContentSharingSharedLinkType | null>(null);
71+
const [currentUserEnterpriseName, setCurrentUserEnterpriseName] = React.useState<string | null>(null);
72+
const [currentUserID, setCurrentUserID] = React.useState<string | null>(null);
73+
const [initialDataErrorMessage, setInitialDataErrorMessage] = React.useState<MessageDescriptor | null>(null);
74+
const [isInitialDataErrorVisible, setIsInitialDataErrorVisible] = React.useState<boolean>(false);
75+
const [collaboratorsList, setCollaboratorsList] = React.useState<CollaboratorListType | null>(null);
76+
const [onAddLink, setOnAddLink] = React.useState<null | SharedLinkUpdateLevelFnType>(null);
77+
const [onRemoveLink, setOnRemoveLink] = React.useState<null | SharedLinkUpdateLevelFnType>(null);
78+
const [changeSharedLinkAccessLevel, setChangeSharedLinkAccessLevel] =
79+
React.useState<null | SharedLinkUpdateLevelFnType>(null);
80+
const [changeSharedLinkPermissionLevel, setChangeSharedLinkPermissionLevel] =
81+
React.useState<null | SharedLinkUpdateLevelFnType>(null);
82+
const [onSubmitSettings, setOnSubmitSettings] = React.useState<null | SharedLinkUpdateSettingsFnType>(null);
83+
const [currentView, setCurrentView] = React.useState<string>(CONTENT_SHARING_VIEWS.UNIFIED_SHARE_MODAL);
84+
const [getContacts, setGetContacts] = React.useState<null | GetContactsFnType>(null);
85+
const [getContactsByEmail, setGetContactsByEmail] = React.useState<null | GetContactsByEmailFnType>(null);
86+
const [sendInvites, setSendInvites] = React.useState<null | SendInvitesFnType>(null);
87+
const [isLoading, setIsLoading] = React.useState<boolean>(true);
88+
89+
// Handle successful GET requests to /files or /folders
90+
const handleGetItemSuccess = React.useCallback((itemData: ContentSharingItemAPIResponse) => {
91+
const { item: itemFromAPI, sharedLink: sharedLinkFromAPI } = convertItemResponse(itemData);
92+
setItem(itemFromAPI);
93+
setSharedLink(sharedLinkFromAPI);
94+
setIsLoading(false);
95+
}, []);
96+
97+
// Handle initial data retrieval errors
98+
const getError = React.useCallback(
99+
(error: AxiosError<Object> | ErrorResponseData) => {
100+
if (isInitialDataErrorVisible) return; // display only one component-level notification at a time
101+
102+
setIsInitialDataErrorVisible(true);
103+
setIsLoading(false);
104+
105+
let errorObject;
106+
if (error.status) {
107+
errorObject = contentSharingMessages[CONTENT_SHARING_ERRORS[error.status]];
108+
} else if (error.response && error.response.status) {
109+
errorObject = contentSharingMessages[CONTENT_SHARING_ERRORS[error.response.status]];
110+
} else {
111+
errorObject = contentSharingMessages.loadingError;
112+
}
113+
setInitialDataErrorMessage(errorObject);
114+
},
115+
[isInitialDataErrorVisible],
116+
);
117+
118+
// Reset state if the API has changed
119+
React.useEffect(() => {
120+
setChangeSharedLinkAccessLevel(null);
121+
setChangeSharedLinkPermissionLevel(null);
122+
setCollaboratorsList(null);
123+
setInitialDataErrorMessage(null);
124+
setCurrentUserID(null);
125+
setCurrentUserEnterpriseName(null);
126+
setIsInitialDataErrorVisible(false);
127+
setIsLoading(true);
128+
setItem(null);
129+
setOnAddLink(null);
130+
setOnRemoveLink(null);
131+
setSharedLink(null);
132+
}, [api]);
133+
134+
// Refresh error state if the uuid has changed
135+
React.useEffect(() => {
136+
setInitialDataErrorMessage(null);
137+
setIsInitialDataErrorVisible(false);
138+
}, [uuid]);
139+
140+
// Get initial data for the item
141+
React.useEffect(() => {
142+
const getItem = () => {
143+
if (itemType === TYPE_FILE) {
144+
api.getFileAPI().getFile(itemID, handleGetItemSuccess, getError, {
145+
fields: CONTENT_SHARING_ITEM_FIELDS,
146+
});
147+
} else if (itemType === TYPE_FOLDER) {
148+
api.getFolderAPI().getFolderFields(itemID, handleGetItemSuccess, getError, {
149+
fields: CONTENT_SHARING_ITEM_FIELDS,
150+
});
151+
}
152+
};
153+
154+
if (api && !isEmpty(api) && !initialDataErrorMessage && isVisible && !item && !sharedLink) {
155+
getItem();
156+
}
157+
}, [api, initialDataErrorMessage, getError, handleGetItemSuccess, isVisible, item, itemID, itemType, sharedLink]);
158+
159+
// Get initial data for the user
160+
React.useEffect(() => {
161+
const getUserSuccess = (userData: User) => {
162+
const { id, userEnterpriseData } = convertUserResponse(userData);
163+
setCurrentUserID(id);
164+
setCurrentUserEnterpriseName(userEnterpriseData.enterpriseName || null);
165+
setSharedLink(prevSharedLink => ({ ...prevSharedLink, ...userEnterpriseData }));
166+
setInitialDataErrorMessage(null);
167+
setIsLoading(false);
168+
};
169+
170+
const getUserData = () => {
171+
api.getUsersAPI(false).getUser(itemID, getUserSuccess, getError, {
172+
params: {
173+
fields: [FIELD_ENTERPRISE, FIELD_HOSTNAME].toString(),
174+
},
175+
});
176+
};
177+
178+
if (api && !isEmpty(api) && !initialDataErrorMessage && item && sharedLink && !currentUserID) {
179+
getUserData();
180+
}
181+
}, [getError, item, itemID, itemType, sharedLink, currentUserID, api, initialDataErrorMessage]);
182+
183+
// Set the getContactsByEmail function. This call is not associated with a banner notification,
184+
// which is why it exists at this level and not in SharingNotification
185+
const getContactsByEmailFn: GetContactsByEmailFnType | null = useContactsByEmail(api, itemID, {
186+
transformUsers: data => convertUserContactsByEmailResponse(data),
187+
});
188+
if (getContactsByEmailFn && !getContactsByEmail) {
189+
setGetContactsByEmail((): GetContactsByEmailFnType => getContactsByEmailFn);
190+
}
191+
192+
// Display a notification if there is an error in retrieving initial data
193+
if (initialDataErrorMessage) {
194+
return isInitialDataErrorVisible ? (
195+
<Internationalize language={language} messages={messages}>
196+
<NotificationsWrapper>
197+
<Notification
198+
onClose={() => setIsInitialDataErrorVisible(false)}
199+
type={TYPE_ERROR}
200+
duration={DURATION_SHORT}
201+
>
202+
<span>
203+
<FormattedMessage {...initialDataErrorMessage} />
204+
</span>
205+
</Notification>
206+
</NotificationsWrapper>
207+
</Internationalize>
208+
) : null;
209+
}
210+
211+
// Ensure that all necessary data has been received before rendering child components.
212+
// If the USM is visible, show the LoadingIndicator; otherwise, show nothing.
213+
// "serverURL" is added to sharedLink after the call to the Users API, so it needs to be checked separately.
214+
if (!item || !sharedLink || !currentUserID || !sharedLink.serverURL) {
215+
return isVisible ? <LoadingIndicator /> : null;
216+
}
217+
218+
const { ownerEmail, ownerID, permissions } = item;
219+
const {
220+
accessLevel = '',
221+
canChangeExpiration = false,
222+
expirationTimestamp,
223+
isDownloadAvailable = false,
224+
serverURL,
225+
} = sharedLink;
226+
return (
227+
<Internationalize language={language} messages={messages}>
228+
<>
229+
<SharingNotification
230+
accessLevel={accessLevel}
231+
api={api}
232+
closeComponent={displayInModal ? () => setIsVisible(false) : noop}
233+
closeSettings={() => setCurrentView(CONTENT_SHARING_VIEWS.UNIFIED_SHARE_MODAL)}
234+
collaboratorsList={collaboratorsList}
235+
currentUserID={currentUserID}
236+
getContacts={getContacts}
237+
isDownloadAvailable={isDownloadAvailable}
238+
itemID={itemID}
239+
itemType={itemType}
240+
onSubmitSettings={onSubmitSettings}
241+
ownerEmail={ownerEmail}
242+
ownerID={ownerID}
243+
permissions={permissions}
244+
sendInvites={sendInvites}
245+
serverURL={serverURL}
246+
setChangeSharedLinkAccessLevel={setChangeSharedLinkAccessLevel}
247+
setChangeSharedLinkPermissionLevel={setChangeSharedLinkPermissionLevel}
248+
setGetContacts={setGetContacts}
249+
setCollaboratorsList={setCollaboratorsList}
250+
setIsLoading={setIsLoading}
251+
setItem={setItem}
252+
setOnAddLink={setOnAddLink}
253+
setOnRemoveLink={setOnRemoveLink}
254+
setOnSubmitSettings={setOnSubmitSettings}
255+
setSendInvites={setSendInvites}
256+
setSharedLink={setSharedLink}
257+
/>
258+
{isVisible && currentView === CONTENT_SHARING_VIEWS.SHARED_LINK_SETTINGS && (
259+
<SharedLinkSettingsModal
260+
isDirectLinkUnavailableDueToDownloadSettings={false}
261+
isDirectLinkUnavailableDueToAccessPolicy={false}
262+
isDirectLinkUnavailableDueToMaliciousContent={false}
263+
isOpen={isVisible}
264+
item={item}
265+
onRequestClose={() => setCurrentView(CONTENT_SHARING_VIEWS.UNIFIED_SHARE_MODAL)}
266+
onSubmit={onSubmitSettings}
267+
submitting={isLoading}
268+
{...sharedLink}
269+
canChangeExpiration={canChangeExpiration && !!currentUserEnterpriseName}
270+
/>
271+
)}
272+
{isVisible && currentView === CONTENT_SHARING_VIEWS.UNIFIED_SHARE_MODAL && (
273+
<UnifiedShareModal
274+
canInvite={sharedLink.canInvite}
275+
config={config}
276+
changeSharedLinkAccessLevel={changeSharedLinkAccessLevel}
277+
changeSharedLinkPermissionLevel={changeSharedLinkPermissionLevel}
278+
collaboratorsList={collaboratorsList}
279+
currentUserID={currentUserID}
280+
displayInModal={displayInModal}
281+
getCollaboratorContacts={getContacts}
282+
getContactsByEmail={getContactsByEmail}
283+
initialDataReceived
284+
inviteePermissions={
285+
itemType === TYPE_FOLDER ? INVITEE_PERMISSIONS_FOLDER : INVITEE_PERMISSIONS_FILE
286+
}
287+
isOpen={isVisible}
288+
item={item}
289+
onAddLink={onAddLink}
290+
onRequestClose={displayInModal ? () => setIsVisible(false) : noop}
291+
onRemoveLink={onRemoveLink}
292+
onSettingsClick={() => setCurrentView(CONTENT_SHARING_VIEWS.SHARED_LINK_SETTINGS)}
293+
sendInvites={sendInvites}
294+
sharedLink={{
295+
...sharedLink,
296+
expirationTimestamp: expirationTimestamp ? expirationTimestamp / 1000 : null,
297+
}} // the USM expects this value in seconds, while the SLSM expects this value in milliseconds
298+
submitting={isLoading}
299+
/>
300+
)}
301+
</>
302+
</Internationalize>
303+
);
304+
}
305+
306+
export default SharingModal;

0 commit comments

Comments
 (0)