diff --git a/package.json b/package.json
index 789bbdb50b..4c6dba7099 100644
--- a/package.json
+++ b/package.json
@@ -140,7 +140,7 @@
"@box/metadata-view": "^0.54.0",
"@box/react-virtualized": "^9.22.3-rc-box.10",
"@box/types": "^0.2.1",
- "@box/unified-share-modal": "^0.48.8",
+ "@box/unified-share-modal": "^0.52.0",
"@box/user-selector": "^1.23.25",
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
"@chromatic-com/storybook": "^4.0.1",
@@ -310,7 +310,7 @@
"@box/metadata-view": "^0.54.0",
"@box/react-virtualized": "^9.22.3-rc-box.10",
"@box/types": "^0.2.1",
- "@box/unified-share-modal": "^0.48.8",
+ "@box/unified-share-modal": "^0.52.0",
"@box/user-selector": "^1.23.25",
"@hapi/address": "^2.1.4",
"@tanstack/react-virtual": "^3.13.12",
diff --git a/src/elements/content-sharing/ContentSharing.js b/src/elements/content-sharing/ContentSharing.js
index 50663c283b..94b27ffe45 100644
--- a/src/elements/content-sharing/ContentSharing.js
+++ b/src/elements/content-sharing/ContentSharing.js
@@ -116,15 +116,18 @@ function ContentSharing({
if (isFeatureEnabled(features, 'contentSharingV2')) {
return (
-
- {children}
-
+ api && (
+
+ {children}
+
+ )
);
}
diff --git a/src/elements/content-sharing/ContentSharingV2.tsx b/src/elements/content-sharing/ContentSharingV2.tsx
index 5391c041f4..925352c544 100644
--- a/src/elements/content-sharing/ContentSharingV2.tsx
+++ b/src/elements/content-sharing/ContentSharingV2.tsx
@@ -1,13 +1,21 @@
import * as React from 'react';
+import isEmpty from 'lodash/isEmpty';
import { UnifiedShareModal } from '@box/unified-share-modal';
+import type { CollaborationRole, Item, SharedLink, User } from '@box/unified-share-modal';
+import API from '../../api';
+import { FIELD_ENTERPRISE, FIELD_HOSTNAME, TYPE_FILE, TYPE_FOLDER } from '../../constants';
import Internationalize from '../common/Internationalize';
import Providers from '../common/Providers';
+import { CONTENT_SHARING_ITEM_FIELDS } from './constants';
+import { convertItemResponse } from './utils';
import type { ItemType, StringMap } from '../../common/types/core';
export interface ContentSharingV2Props {
+ /** api - API instance */
+ api: API;
/** children - Children for the element to open the Unified Share Modal */
children?: React.ReactElement;
/** itemID - Box file or folder ID */
@@ -15,25 +23,124 @@ export interface ContentSharingV2Props {
/** itemType - "file" or "folder" */
itemType: ItemType;
/** hasProviders - Whether the element has providers for USM already */
- hasProviders: boolean;
+ hasProviders?: boolean;
/** language - Language used for the element */
language?: string;
/** messages - Localized strings used by the element */
messages?: StringMap;
}
-function ContentSharingV2({ children, itemID, itemType, hasProviders, language, messages }: ContentSharingV2Props) {
- // Retrieve item from API later
- const mockItem = {
- id: itemID,
- name: 'Box Development Guide.pdf',
- type: itemType,
+function ContentSharingV2({
+ api,
+ children,
+ itemID,
+ itemType,
+ hasProviders,
+ language,
+ messages,
+}: ContentSharingV2Props) {
+ const [item, setItem] = React.useState- (null);
+ const [sharedLink, setSharedLink] = React.useState(null);
+ const [currentUser, setCurrentUser] = React.useState(null);
+ const [collaborationRoles, setCollaborationRoles] = React.useState(null);
+
+ // Handle successful GET requests to /files or /folders
+ const handleGetItemSuccess = React.useCallback(itemData => {
+ const {
+ collaborationRoles: collaborationRolesFromAPI,
+ item: itemFromAPI,
+ sharedLink: sharedLinkFromAPI,
+ } = convertItemResponse(itemData);
+ setItem(itemFromAPI);
+ setSharedLink(sharedLinkFromAPI);
+ setCollaborationRoles(collaborationRolesFromAPI);
+ }, []);
+
+ // Reset state if the API has changed
+ React.useEffect(() => {
+ setItem(null);
+ setSharedLink(null);
+ setCurrentUser(null);
+ setCollaborationRoles(null);
+ }, [api]);
+
+ // Get initial data for the item
+ React.useEffect(() => {
+ const getItem = () => {
+ if (itemType === TYPE_FILE) {
+ api.getFileAPI().getFile(
+ itemID,
+ handleGetItemSuccess,
+ {},
+ {
+ fields: CONTENT_SHARING_ITEM_FIELDS,
+ },
+ );
+ } else if (itemType === TYPE_FOLDER) {
+ api.getFolderAPI().getFolderFields(
+ itemID,
+ handleGetItemSuccess,
+ {},
+ {
+ fields: CONTENT_SHARING_ITEM_FIELDS,
+ },
+ );
+ }
+ };
+
+ if (api && !isEmpty(api) && !item && !sharedLink) {
+ getItem();
+ }
+ }, [api, item, itemID, itemType, sharedLink, handleGetItemSuccess]);
+
+ // Get initial data for the user
+ React.useEffect(() => {
+ const getUserSuccess = userData => {
+ const { enterprise, id } = userData;
+ setCurrentUser({
+ id,
+ enterprise: {
+ name: enterprise ? enterprise.name : '',
+ },
+ });
+ };
+
+ const getUserData = () => {
+ api.getUsersAPI(false).getUser(
+ itemID,
+ getUserSuccess,
+ {},
+ {
+ params: {
+ fields: [FIELD_ENTERPRISE, FIELD_HOSTNAME].toString(),
+ },
+ },
+ );
+ };
+
+ if (api && !isEmpty(api) && item && sharedLink && !currentUser) {
+ getUserData();
+ }
+ }, [api, currentUser, item, itemID, itemType, sharedLink]);
+
+ const config = {
+ sharedLinkEmail: false,
};
return (
- {children}
+ {item && (
+
+ {children}
+
+ )}
);
diff --git a/src/elements/content-sharing/__tests__/ContentSharingV2.test.tsx b/src/elements/content-sharing/__tests__/ContentSharingV2.test.tsx
new file mode 100644
index 0000000000..f8165d9064
--- /dev/null
+++ b/src/elements/content-sharing/__tests__/ContentSharingV2.test.tsx
@@ -0,0 +1,132 @@
+import React from 'react';
+import { render, RenderResult, screen, waitFor } from '@testing-library/react';
+
+import {
+ DEFAULT_ITEM_API_RESPONSE,
+ DEFAULT_USER_API_RESPONSE,
+ MOCK_ITEM,
+ MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK,
+ MOCK_ITEM_API_RESPONSE_WITH_CLASSIFICATION,
+} from '../utils/__mocks__/ContentSharingV2Mocks';
+import { CONTENT_SHARING_ITEM_FIELDS } from '../constants';
+
+import ContentSharingV2 from '../ContentSharingV2';
+
+const createAPIMock = (fileAPI, folderAPI, usersAPI) => ({
+ getFileAPI: jest.fn().mockReturnValue(fileAPI),
+ getFolderAPI: jest.fn().mockReturnValue(folderAPI),
+ getUsersAPI: jest.fn().mockReturnValue(usersAPI),
+});
+
+const createSuccessMock = responseFromAPI => (id, successFn) => {
+ return Promise.resolve(responseFromAPI).then(response => {
+ successFn(response);
+ });
+};
+
+const getDefaultUserMock = jest.fn().mockImplementation(createSuccessMock(DEFAULT_USER_API_RESPONSE));
+const getDefaultFileMock = jest.fn().mockImplementation(createSuccessMock(DEFAULT_ITEM_API_RESPONSE));
+const getFileMockWithSharedLink = jest
+ .fn()
+ .mockImplementation(createSuccessMock(MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK));
+const getFileMockWithClassification = jest
+ .fn()
+ .mockImplementation(createSuccessMock(MOCK_ITEM_API_RESPONSE_WITH_CLASSIFICATION));
+const getDefaultFolderMock = jest.fn().mockImplementation(createSuccessMock(DEFAULT_ITEM_API_RESPONSE));
+const defaultAPIMock = createAPIMock(
+ { getFile: getDefaultFileMock },
+ { getFolderFields: getDefaultFolderMock },
+ { getUser: getDefaultUserMock },
+);
+
+const getWrapper = (props): RenderResult =>
+ render(
+ ,
+ );
+
+describe('elements/content-sharing/ContentSharingV2', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('should see the correct elements for files', async () => {
+ getWrapper({});
+ await waitFor(() => {
+ expect(getDefaultFileMock).toHaveBeenCalledWith(
+ MOCK_ITEM.id,
+ expect.any(Function),
+ {},
+ {
+ fields: CONTENT_SHARING_ITEM_FIELDS,
+ },
+ );
+ });
+
+ expect(screen.getByRole('heading', { name: 'Share ‘Box Development Guide.pdf’' })).toBeVisible();
+ expect(screen.getByRole('combobox', { name: 'Invite People' })).toBeVisible();
+ expect(screen.getByRole('switch', { name: 'Shared link' })).toBeVisible();
+ });
+
+ test('should see the correct elements for folders', async () => {
+ getWrapper({ itemType: 'folder' });
+ await waitFor(() => {
+ expect(getDefaultFolderMock).toHaveBeenCalledWith(
+ MOCK_ITEM.id,
+ expect.any(Function),
+ {},
+ {
+ fields: CONTENT_SHARING_ITEM_FIELDS,
+ },
+ );
+ });
+
+ expect(screen.getByRole('heading', { name: 'Share ‘Box Development Guide.pdf’' })).toBeVisible();
+ expect(screen.getByRole('combobox', { name: 'Invite People' })).toBeVisible();
+ expect(screen.getByRole('switch', { name: 'Shared link' })).toBeVisible();
+ });
+
+ test('should see the shared link elements if shared link is present', async () => {
+ getWrapper({
+ api: createAPIMock({ getFile: getFileMockWithSharedLink }, null, { getUser: getDefaultUserMock }),
+ });
+ await waitFor(() => {
+ expect(getFileMockWithSharedLink).toHaveBeenCalledWith(
+ MOCK_ITEM.id,
+ expect.any(Function),
+ {},
+ {
+ fields: CONTENT_SHARING_ITEM_FIELDS,
+ },
+ );
+ });
+
+ expect(await screen.findByLabelText('Shared link URL')).toBeVisible();
+ expect(await screen.findByRole('button', { name: 'People with the link' })).toBeVisible();
+ expect(await screen.findByRole('button', { name: 'Can view and download' })).toBeVisible();
+ expect(screen.getByRole('button', { name: 'Link Settings' })).toBeVisible();
+ expect(screen.getByRole('button', { name: 'Copy' })).toBeVisible();
+ });
+
+ test('should see the classification elements if classification is present', async () => {
+ getWrapper({
+ api: createAPIMock({ getFile: getFileMockWithClassification }, null, { getUser: getDefaultUserMock }),
+ });
+ await waitFor(() => {
+ expect(getFileMockWithClassification).toHaveBeenCalledWith(
+ MOCK_ITEM.id,
+ expect.any(Function),
+ {},
+ {
+ fields: CONTENT_SHARING_ITEM_FIELDS,
+ },
+ );
+ });
+ expect(screen.getByText('BLUE')).toBeVisible();
+ });
+});
diff --git a/src/elements/content-sharing/stories/ContentSharingV2.stories.tsx b/src/elements/content-sharing/stories/ContentSharingV2.stories.tsx
index 166ca26ac5..e653601016 100644
--- a/src/elements/content-sharing/stories/ContentSharingV2.stories.tsx
+++ b/src/elements/content-sharing/stories/ContentSharingV2.stories.tsx
@@ -1,13 +1,22 @@
import * as React from 'react';
+
import { TYPE_FILE, TYPE_FOLDER } from '../../../constants';
+import { mockAPIWithSharedLink, mockAPIWithoutSharedLink } from '../utils/__mocks__/ContentSharingV2Mocks';
import ContentSharingV2 from '../ContentSharingV2';
export const basic = {};
+export const withSharedLink = {
+ args: {
+ api: mockAPIWithSharedLink,
+ },
+};
+
export default {
title: 'Elements/ContentSharingV2',
component: ContentSharingV2,
args: {
+ api: mockAPIWithoutSharedLink,
children: ,
itemType: TYPE_FILE,
itemID: global.FILE_ID,
diff --git a/src/elements/content-sharing/stories/tests/ContentSharingV2-visual.stories.tsx b/src/elements/content-sharing/stories/tests/ContentSharingV2-visual.stories.tsx
index f49a24b1dd..3558c60975 100644
--- a/src/elements/content-sharing/stories/tests/ContentSharingV2-visual.stories.tsx
+++ b/src/elements/content-sharing/stories/tests/ContentSharingV2-visual.stories.tsx
@@ -1,16 +1,26 @@
+import * as React from 'react';
import { TYPE_FILE } from '../../../../constants';
+import { mockAPIWithSharedLink, mockAPIWithoutSharedLink } from '../../utils/__mocks__/ContentSharingV2Mocks';
import ContentSharingV2 from '../../ContentSharingV2';
export const withModernization = {
args: {
+ api: mockAPIWithoutSharedLink,
enableModernizedComponents: true,
},
};
+export const withSharedLink = {
+ args: {
+ api: mockAPIWithSharedLink,
+ },
+};
+
export default {
title: 'Elements/ContentSharingV2/tests/visual-regression-tests',
component: ContentSharingV2,
args: {
+ children: ,
itemType: TYPE_FILE,
itemID: global.FILE_ID,
},
diff --git a/src/elements/content-sharing/types.js b/src/elements/content-sharing/types.js
index 8181e28ddf..130328a442 100644
--- a/src/elements/content-sharing/types.js
+++ b/src/elements/content-sharing/types.js
@@ -1,4 +1,6 @@
// @flow
+import type { CollaborationRole, Item, SharedLink } from '@box/unified-share-modal';
+
import type {
Access,
BoxItemClassification,
@@ -152,3 +154,9 @@ export type ConvertCollabOptions = {
isCurrentUserOwner: boolean,
ownerEmail: ?string,
};
+
+export interface ItemData {
+ collaborationRoles: CollaborationRole[];
+ item: Item;
+ sharedLink: SharedLink;
+}
diff --git a/src/elements/content-sharing/utils/__mocks__/ContentSharingV2Mocks.js b/src/elements/content-sharing/utils/__mocks__/ContentSharingV2Mocks.js
new file mode 100644
index 0000000000..fd0eeb6717
--- /dev/null
+++ b/src/elements/content-sharing/utils/__mocks__/ContentSharingV2Mocks.js
@@ -0,0 +1,97 @@
+export const MOCK_PERMISSIONS = {
+ can_download: true,
+ can_invite_collaborator: true,
+ can_set_share_access: true,
+ can_share: true,
+};
+
+export const MOCK_CLASSIFICATION = {
+ color: '#91c2fd',
+ definition: 'Blue classification',
+ name: 'Blue',
+};
+
+export const MOCK_ITEM = {
+ id: '123456789',
+ name: 'Box Development Guide.pdf',
+ type: 'file',
+};
+
+export const MOCK_SHARED_LINK = {
+ access: 'open',
+ effective_permission: 'can_download',
+ is_password_enabled: true,
+ unshared_at: 1704067200000,
+ url: 'https://example.com/shared-link',
+ vanity_name: 'vanity-name',
+ vanity_url: 'https://example.com/vanity-url',
+};
+
+export const DEFAULT_USER_API_RESPONSE = {
+ id: '123',
+ enterprise: {
+ name: 'Parrot Enterprise',
+ },
+};
+
+export const DEFAULT_ITEM_API_RESPONSE = {
+ allowed_invitee_roles: ['editor', 'viewer'],
+ allowed_shared_link_access_levels: ['open', 'company', 'collaborators'],
+ classification: null,
+ id: MOCK_ITEM.id,
+ name: MOCK_ITEM.name,
+ permissions: MOCK_PERMISSIONS,
+ shared_link: null,
+ shared_link_features: { password: true },
+ type: MOCK_ITEM.type,
+};
+
+export const MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK = {
+ ...DEFAULT_ITEM_API_RESPONSE,
+ shared_link: MOCK_SHARED_LINK,
+};
+
+export const MOCK_ITEM_API_RESPONSE_WITH_CLASSIFICATION = {
+ ...DEFAULT_ITEM_API_RESPONSE,
+ classification: MOCK_CLASSIFICATION,
+};
+
+// Mock API class for ContentSharingV2 storybook
+export const createMockAPI = (itemResponse = DEFAULT_ITEM_API_RESPONSE, userResponse = DEFAULT_USER_API_RESPONSE) => {
+ const mockFileAPI = {
+ getFile: (itemID, successCallback) => {
+ // Simulate async behavior
+ setTimeout(() => {
+ successCallback(itemResponse);
+ }, 100);
+ },
+ };
+
+ const mockFolderAPI = {
+ getFolderFields: (itemID, successCallback) => {
+ // Simulate async behavior
+ setTimeout(() => {
+ successCallback(itemResponse);
+ }, 100);
+ },
+ };
+
+ const mockUsersAPI = {
+ getUser: (itemID, successCallback) => {
+ // Simulate async behavior
+ setTimeout(() => {
+ successCallback(userResponse);
+ }, 100);
+ },
+ };
+
+ return {
+ getFileAPI: () => mockFileAPI,
+ getFolderAPI: () => mockFolderAPI,
+ getUsersAPI: () => mockUsersAPI,
+ };
+};
+
+// Pre-configured mock APIs for different scenarios
+export const mockAPIWithSharedLink = createMockAPI(MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK);
+export const mockAPIWithoutSharedLink = createMockAPI(DEFAULT_ITEM_API_RESPONSE);
diff --git a/src/elements/content-sharing/utils/__tests__/convertItemResponse.test.ts b/src/elements/content-sharing/utils/__tests__/convertItemResponse.test.ts
new file mode 100644
index 0000000000..077ab26a19
--- /dev/null
+++ b/src/elements/content-sharing/utils/__tests__/convertItemResponse.test.ts
@@ -0,0 +1,115 @@
+import {
+ DEFAULT_ITEM_API_RESPONSE,
+ MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK,
+ MOCK_ITEM_API_RESPONSE_WITH_CLASSIFICATION,
+} from '../__mocks__/ContentSharingV2Mocks';
+import { convertItemResponse } from '../convertItemResponse';
+
+jest.mock('../getAllowedAccessLevels', () => ({
+ getAllowedAccessLevels: jest.fn().mockReturnValue(['open', 'company', 'collaborators']),
+}));
+
+jest.mock('../getAllowedPermissionLevels', () => ({
+ getAllowedPermissionLevels: jest.fn().mockReturnValue(['canDownload', 'canPreview']),
+}));
+
+describe('convertItemResponse', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('should convert basic item without shared link', () => {
+ const result = convertItemResponse(DEFAULT_ITEM_API_RESPONSE);
+ expect(result).toEqual({
+ collaborationRoles: [{ id: 'editor' }, { id: 'viewer' }],
+ item: {
+ id: '123456789',
+ classification: undefined,
+ name: 'Box Development Guide.pdf',
+ permissions: {
+ canInviteCollaborator: true,
+ canSetShareAccess: true,
+ canShare: true,
+ },
+ type: 'file',
+ },
+ });
+ });
+
+ test('should handle folder type', () => {
+ const MOCK_ITEM_API_RESPONSE_WITH_FOLDER_TYPE = {
+ ...DEFAULT_ITEM_API_RESPONSE,
+ type: 'folder',
+ };
+ const result = convertItemResponse(MOCK_ITEM_API_RESPONSE_WITH_FOLDER_TYPE);
+ expect(result.item.type).toBe('folder');
+ });
+
+ test('should handle item with classification', () => {
+ const result = convertItemResponse(MOCK_ITEM_API_RESPONSE_WITH_CLASSIFICATION);
+ expect(result.item.classification).toEqual({
+ colorId: 4,
+ definition: 'Blue classification',
+ name: 'Blue',
+ });
+ });
+
+ describe('shared link settings', () => {
+ test('should convert item with shared link', () => {
+ const result = convertItemResponse(MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK);
+ expect(result.sharedLink).toEqual({
+ access: 'open',
+ accessLevels: ['open', 'company', 'collaborators'],
+ expiresAt: 1704067200000,
+ permission: 'can_download',
+ permissionLevels: ['canDownload', 'canPreview'],
+ settings: {
+ canChangeDownload: true,
+ canChangeExpiration: true,
+ canChangePassword: true,
+ canChangeVanityName: false,
+ isDownloadAvailable: true,
+ isDownloadEnabled: true,
+ isPasswordAvailable: true,
+ isPasswordEnabled: true,
+ },
+ url: 'https://example.com/shared-link',
+ vanityDomain: 'https://example.com/vanity-url',
+ vanityName: 'vanity-name',
+ });
+ });
+
+ test('should convert shared link settings correctly if user cannot change access level', () => {
+ const MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK_WITH_PERMISSIONS = {
+ ...MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK,
+ permissions: {
+ ...MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK.permissions,
+ can_set_share_access: false,
+ },
+ };
+ const result = convertItemResponse(MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK_WITH_PERMISSIONS);
+ expect(result.sharedLink.settings.canChangeDownload).toEqual(false);
+ expect(result.sharedLink.settings.canChangePassword).toEqual(false);
+ expect(result.sharedLink.settings.canChangeExpiration).toEqual(false);
+ });
+
+ test('should convert shared link settings correctly if user does not have permissions', () => {
+ const MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK_WITH_PERMISSIONS = {
+ ...MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK,
+ allowed_invitee_roles: ['viewer'],
+ shared_link: {
+ ...MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK.shared_link,
+ access: 'collaborators',
+ },
+ shared_link_features: { password: false },
+ permissions: {
+ ...MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK.permissions,
+ },
+ };
+ const result = convertItemResponse(MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK_WITH_PERMISSIONS);
+ expect(result.sharedLink.settings.canChangeDownload).toEqual(false);
+ expect(result.sharedLink.settings.canChangePassword).toEqual(false);
+ expect(result.sharedLink.settings.canChangeExpiration).toEqual(false);
+ });
+ });
+});
diff --git a/src/elements/content-sharing/utils/__tests__/getAllowedAccessLevels.test.ts b/src/elements/content-sharing/utils/__tests__/getAllowedAccessLevels.test.ts
new file mode 100644
index 0000000000..169594487d
--- /dev/null
+++ b/src/elements/content-sharing/utils/__tests__/getAllowedAccessLevels.test.ts
@@ -0,0 +1,24 @@
+import { ACCESS_COLLAB, ACCESS_COMPANY, ACCESS_OPEN } from '../../../../constants';
+import { getAllowedAccessLevels } from '../getAllowedAccessLevels';
+
+describe('getAllowedAccessLevels', () => {
+ test('should return default access levels when no levels parameter is provided', () => {
+ const result = getAllowedAccessLevels();
+ expect(result).toEqual([ACCESS_OPEN, ACCESS_COMPANY, ACCESS_COLLAB]);
+ });
+
+ test('should return empty array when levels parameter is empty array', () => {
+ const result = getAllowedAccessLevels([]);
+ expect(result).toEqual([]);
+ });
+
+ test.each([
+ [[ACCESS_OPEN, ACCESS_COMPANY, ACCESS_COLLAB]],
+ [[ACCESS_OPEN, ACCESS_COMPANY]],
+ [[ACCESS_OPEN]],
+ [[ACCESS_COMPANY]],
+ ])('should return the same levels as provided', levels => {
+ const result = getAllowedAccessLevels(levels);
+ expect(result).toEqual(levels);
+ });
+});
diff --git a/src/elements/content-sharing/utils/__tests__/getAllowedPermissionLevels.test.ts b/src/elements/content-sharing/utils/__tests__/getAllowedPermissionLevels.test.ts
new file mode 100644
index 0000000000..65963a3615
--- /dev/null
+++ b/src/elements/content-sharing/utils/__tests__/getAllowedPermissionLevels.test.ts
@@ -0,0 +1,28 @@
+import { PERMISSION_CAN_DOWNLOAD, PERMISSION_CAN_PREVIEW } from '../../../../constants';
+import { getAllowedPermissionLevels } from '../getAllowedPermissionLevels';
+
+describe('getAllowedPermissionLevels', () => {
+ test('should return both permission levels when all conditions are met', () => {
+ const result = getAllowedPermissionLevels(true, true, PERMISSION_CAN_DOWNLOAD);
+ expect(result).toEqual([PERMISSION_CAN_DOWNLOAD, PERMISSION_CAN_PREVIEW]);
+ });
+
+ test.each([PERMISSION_CAN_DOWNLOAD, PERMISSION_CAN_PREVIEW])(
+ 'should return only current permission when cannot change access level',
+ permission => {
+ const result = getAllowedPermissionLevels(false, true, permission);
+ expect(result).toEqual([permission]);
+ },
+ );
+
+ test('should exclude download permission when download setting is not available', () => {
+ const result = getAllowedPermissionLevels(true, false, PERMISSION_CAN_DOWNLOAD);
+ expect(result).toEqual([PERMISSION_CAN_PREVIEW]);
+ });
+
+ test('should return empty array for unknown permission values when cannot change access level', () => {
+ const unknownPermission = 'unknown_permission';
+ const result = getAllowedPermissionLevels(false, true, unknownPermission);
+ expect(result).toEqual([]);
+ });
+});
diff --git a/src/elements/content-sharing/utils/constants.ts b/src/elements/content-sharing/utils/constants.ts
new file mode 100644
index 0000000000..71ee401eab
--- /dev/null
+++ b/src/elements/content-sharing/utils/constants.ts
@@ -0,0 +1,31 @@
+import {
+ CLASSIFICATION_COLOR_ID_0,
+ CLASSIFICATION_COLOR_ID_1,
+ CLASSIFICATION_COLOR_ID_2,
+ CLASSIFICATION_COLOR_ID_3,
+ CLASSIFICATION_COLOR_ID_4,
+ CLASSIFICATION_COLOR_ID_5,
+ CLASSIFICATION_COLOR_ID_6,
+ CLASSIFICATION_COLOR_ID_7,
+} from '../../../features/classification/constants';
+import {
+ bdlDarkBlue50,
+ bdlGray20,
+ bdlGreenLight50,
+ bdlLightBlue50,
+ bdlOrange50,
+ bdlPurpleRain50,
+ bdlWatermelonRed50,
+ bdlYellow50,
+} from '../../../styles/variables';
+
+export const API_TO_USM_CLASSIFICATION_COLORS_MAP = {
+ [bdlYellow50]: CLASSIFICATION_COLOR_ID_0,
+ [bdlOrange50]: CLASSIFICATION_COLOR_ID_1,
+ [bdlWatermelonRed50]: CLASSIFICATION_COLOR_ID_2,
+ [bdlPurpleRain50]: CLASSIFICATION_COLOR_ID_3,
+ [bdlLightBlue50]: CLASSIFICATION_COLOR_ID_4,
+ [bdlDarkBlue50]: CLASSIFICATION_COLOR_ID_5,
+ [bdlGreenLight50]: CLASSIFICATION_COLOR_ID_6,
+ [bdlGray20]: CLASSIFICATION_COLOR_ID_7,
+};
diff --git a/src/elements/content-sharing/utils/convertItemResponse.ts b/src/elements/content-sharing/utils/convertItemResponse.ts
new file mode 100644
index 0000000000..060ee982b4
--- /dev/null
+++ b/src/elements/content-sharing/utils/convertItemResponse.ts
@@ -0,0 +1,99 @@
+import { ACCESS_COLLAB, INVITEE_ROLE_EDITOR, PERMISSION_CAN_DOWNLOAD } from '../../../constants';
+import { getAllowedAccessLevels } from './getAllowedAccessLevels';
+import { getAllowedPermissionLevels } from './getAllowedPermissionLevels';
+import { API_TO_USM_CLASSIFICATION_COLORS_MAP } from '../utils/constants';
+
+import type { ContentSharingItemAPIResponse, ItemData } from '../types';
+
+export const convertItemResponse = (itemAPIData: ContentSharingItemAPIResponse): ItemData => {
+ const {
+ allowed_invitee_roles,
+ allowed_shared_link_access_levels,
+ classification,
+ id,
+ name,
+ permissions,
+ shared_link,
+ shared_link_features,
+ type,
+ } = itemAPIData;
+
+ const { password: isPasswordAvailable } = shared_link_features;
+
+ const {
+ can_download: isDownloadSettingAvailable,
+ can_invite_collaborator: canInvite,
+ can_set_share_access: canChangeAccessLevel,
+ can_share: canShare,
+ } = permissions;
+
+ // Convert classification data for the item if available
+ let classificationData;
+ if (classification) {
+ const { color, definition, name: classificationName } = classification;
+ classificationData = {
+ colorId: API_TO_USM_CLASSIFICATION_COLORS_MAP[color],
+ definition,
+ name: classificationName,
+ };
+ }
+
+ const isEditAllowed = allowed_invitee_roles.indexOf(INVITEE_ROLE_EDITOR) !== -1;
+
+ let sharedLink;
+ if (shared_link) {
+ const {
+ access,
+ effective_permission: permission,
+ is_password_enabled: isPasswordEnabled,
+ unshared_at: expirationTimestamp,
+ url,
+ vanity_name: vanityName,
+ vanity_url: vanityUrl,
+ } = shared_link;
+
+ const isDownloadAllowed = permission === PERMISSION_CAN_DOWNLOAD;
+ const canChangeDownload = canChangeAccessLevel && isDownloadSettingAvailable && access !== ACCESS_COLLAB; // access must be "company" or "open"
+ const canChangePassword = canChangeAccessLevel && isPasswordAvailable;
+ const canChangeExpiration = canChangeAccessLevel && isEditAllowed;
+
+ sharedLink = {
+ access,
+ accessLevels: getAllowedAccessLevels(allowed_shared_link_access_levels),
+ expiresAt: expirationTimestamp,
+ permission,
+ permissionLevels: getAllowedPermissionLevels(canChangeAccessLevel, isDownloadSettingAvailable, permission),
+ settings: {
+ canChangeDownload,
+ canChangeExpiration,
+ canChangePassword,
+ canChangeVanityName: false, // vanity URLs cannot be set via the API,
+ isDownloadAvailable: isDownloadSettingAvailable,
+ isDownloadEnabled: isDownloadAllowed,
+ isPasswordAvailable: isPasswordAvailable ?? false,
+ isPasswordEnabled,
+ },
+ url,
+ vanityDomain: vanityUrl,
+ vanityName: vanityName || '',
+ };
+ }
+
+ const collaborationRoles = allowed_invitee_roles.map(role => ({ id: role }));
+
+ return {
+ collaborationRoles,
+ item: {
+ id,
+ classification: classificationData,
+ name,
+ permissions: {
+ canInviteCollaborator: !!canInvite,
+ canSetShareAccess: canChangeAccessLevel,
+ canShare: !!canShare,
+ },
+ type,
+ },
+ sharedLink,
+ };
+};
diff --git a/src/elements/content-sharing/utils/getAllowedAccessLevels.ts b/src/elements/content-sharing/utils/getAllowedAccessLevels.ts
new file mode 100644
index 0000000000..8e2602371f
--- /dev/null
+++ b/src/elements/content-sharing/utils/getAllowedAccessLevels.ts
@@ -0,0 +1,6 @@
+import { ACCESS_COLLAB, ACCESS_COMPANY, ACCESS_OPEN } from '../../../constants';
+
+export const getAllowedAccessLevels = (levels?: Array): Array | null => {
+ if (!levels) return [ACCESS_OPEN, ACCESS_COMPANY, ACCESS_COLLAB];
+ return [...levels];
+};
diff --git a/src/elements/content-sharing/utils/getAllowedPermissionLevels.ts b/src/elements/content-sharing/utils/getAllowedPermissionLevels.ts
new file mode 100644
index 0000000000..2922386832
--- /dev/null
+++ b/src/elements/content-sharing/utils/getAllowedPermissionLevels.ts
@@ -0,0 +1,21 @@
+import { PERMISSION_CAN_DOWNLOAD, PERMISSION_CAN_PREVIEW } from '../../../constants';
+
+export const getAllowedPermissionLevels = (
+ canChangeAccessLevel,
+ isDownloadSettingAvailable,
+ permission,
+): Array => {
+ let allowedPermissionLevels = [PERMISSION_CAN_DOWNLOAD, PERMISSION_CAN_PREVIEW];
+
+ if (!canChangeAccessLevel) {
+ // remove all but current level
+ allowedPermissionLevels = allowedPermissionLevels.filter(level => level === permission);
+ }
+
+ // if we cannot set the download value, we remove this option from the dropdown
+ if (!isDownloadSettingAvailable) {
+ allowedPermissionLevels = allowedPermissionLevels.filter(level => level !== PERMISSION_CAN_DOWNLOAD);
+ }
+
+ return allowedPermissionLevels;
+};
diff --git a/src/elements/content-sharing/utils/index.ts b/src/elements/content-sharing/utils/index.ts
new file mode 100644
index 0000000000..38b674e241
--- /dev/null
+++ b/src/elements/content-sharing/utils/index.ts
@@ -0,0 +1,3 @@
+export { convertItemResponse } from './convertItemResponse';
+export { getAllowedAccessLevels } from './getAllowedAccessLevels';
+export { getAllowedPermissionLevels } from './getAllowedPermissionLevels';
diff --git a/yarn.lock b/yarn.lock
index b8c82fd7b0..c20a76eefc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1419,7 +1419,7 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
-"@box/blueprint-web-assets@^4.68.6":
+"@box/blueprint-web-assets@^4.68.0", "@box/blueprint-web-assets@^4.68.6":
version "4.68.6"
resolved "https://registry.yarnpkg.com/@box/blueprint-web-assets/-/blueprint-web-assets-4.68.6.tgz#81c27616687794032e9dc7ece6857797188e5130"
integrity sha512-2UrvvlCzE/PkgQ3yQldqlZxCF6pUXp+UKOuvFGAmAm2B1hWw0v3BfiPDTTJSRfGAnukNnpnItjdMkaq/qXKOpA==
@@ -1549,10 +1549,10 @@
resolved "https://registry.yarnpkg.com/@box/types/-/types-0.2.1.tgz#cd0a3915b2306e4cf581f6091b95f5d2db75ea60"
integrity sha512-wd6nRR9QxBl7lYKJ/Hix0AKg1PNC3leZWOJ9Nt+d4j45WxCYBiCemZAtY2ekL5BITpVw8vlLmquzSpPhDTeO5A==
-"@box/unified-share-modal@^0.48.8":
- version "0.48.8"
- resolved "https://registry.yarnpkg.com/@box/unified-share-modal/-/unified-share-modal-0.48.8.tgz#d166ec081788e142fd90332f1c96bc17890d79a8"
- integrity sha512-zF1kAc9inyQnKkMPyghiRkpqeA5w4NO3fyuRRq0QIXwP0Xt8edZsD/sLj2sXmRzNw/9W2Qz/7wih3p+xzrEUxg==
+"@box/unified-share-modal@^0.52.0":
+ version "0.52.0"
+ resolved "https://registry.yarnpkg.com/@box/unified-share-modal/-/unified-share-modal-0.52.0.tgz#5ebfb1c9246789ce4650efc9b19283de0c492f71"
+ integrity sha512-85/xr47n9uCNwJ3nMq5AEGHJ6DOUrClh9ARbwaoJeR39x0sTjv0JGIBAmK4yM2rhPMGZO2jnnBcZ8wbZ+yVoHw==
"@box/user-selector@^1.23.25":
version "1.23.25"