From 469f17e773f1e50c12055f75ad37eb31d5143474 Mon Sep 17 00:00:00 2001 From: reneshen0328 Date: Fri, 22 May 2026 16:10:06 -0700 Subject: [PATCH 1/7] fix(content-sharing): render external badge logic --- src/elements/content-sharing/utils/convertCollaborators.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/elements/content-sharing/utils/convertCollaborators.ts b/src/elements/content-sharing/utils/convertCollaborators.ts index f9dbcc27b6..51ab0f2692 100644 --- a/src/elements/content-sharing/utils/convertCollaborators.ts +++ b/src/elements/content-sharing/utils/convertCollaborators.ts @@ -48,8 +48,10 @@ export const convertCollab = ({ return null; } + // External collaborator icons will only be displayed in the USM if the current user owns + // the item and if the collaborator's email domain differs from the owner's email domain. const isExternal = - !isCurrentUserOwner && !!collabEmail && !!ownerEmailDomain && collabEmail.split('@')[1] !== ownerEmailDomain; + isCurrentUserOwner && !!collabEmail && !!ownerEmailDomain && collabEmail.split('@')[1] !== ownerEmailDomain; const avatarUrl = avatarUrlMap ? avatarUrlMap[collabId] : undefined; return { From 4cda333b54292fe1c4f8af68a10b2f2ef32be4d9 Mon Sep 17 00:00:00 2001 From: reneshen0328 Date: Fri, 22 May 2026 16:17:32 -0700 Subject: [PATCH 2/7] fix: update test --- .../utils/__tests__/convertCollaborators.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts b/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts index fccb6ec13f..553b8654f8 100644 --- a/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts +++ b/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts @@ -119,7 +119,7 @@ describe('convertCollaborators', () => { avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[5], currentUserId: mockOwnerId, - isCurrentUserOwner: false, + isCurrentUserOwner: true, ownerEmailDomain, }); @@ -225,7 +225,7 @@ describe('convertCollaborators', () => { avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[2], currentUserId: mockOwnerId, - isCurrentUserOwner: false, + isCurrentUserOwner: true, ownerEmailDomain, }); @@ -310,7 +310,7 @@ describe('convertCollaborators', () => { hasCustomAvatar: false, id: '124', isCurrentUser: false, - isExternal: false, + isExternal: true, isPending: false, name: 'Raccoon Queen', role: 'viewer', @@ -336,7 +336,7 @@ describe('convertCollaborators', () => { hasCustomAvatar: false, id: '127', isCurrentUser: false, - isExternal: false, + isExternal: true, isPending: true, name: 'bbear@external.example.com', role: 'editor', From 6ad6ef3ce62b1a9e5ebf1fd87aa0a9bb5db8224f Mon Sep 17 00:00:00 2001 From: reneshen0328 Date: Fri, 22 May 2026 17:49:34 -0700 Subject: [PATCH 3/7] fix: update external badge render logic to match webapp --- src/constants.js | 1 + .../content-sharing/ContentSharingV2.tsx | 14 ++++++--- .../apis/__tests__/fetchCurrentUser.test.ts | 4 +-- .../content-sharing/apis/fetchCurrentUser.ts | 4 +-- .../hooks/__tests__/useSharingService.test.ts | 10 +++--- .../hooks/useSharingService.ts | 8 ++--- .../__tests__/convertCollaborators.test.ts | 31 +++++++++++-------- .../utils/convertCollaborators.ts | 25 +++++++++------ 8 files changed, 56 insertions(+), 41 deletions(-) diff --git a/src/constants.js b/src/constants.js index 0f8a4b8777..da51241d2d 100644 --- a/src/constants.js +++ b/src/constants.js @@ -171,6 +171,7 @@ export const FIELD_UPLOADER_DISPLAY_NAME: 'uploader_display_name' = 'uploader_di export const FIELD_CLASSIFICATION: 'classification' = 'classification'; export const FIELD_ENTERPRISE: 'enterprise' = 'enterprise'; export const FIELD_HOSTNAME: 'hostname' = 'hostname'; +export const FIELD_LOGIN: 'login' = 'login'; /* ----------------------- Item-Prefixed Fields for MD Query API --------------------------- */ const ITEM_PREFIX = 'item.'; diff --git a/src/elements/content-sharing/ContentSharingV2.tsx b/src/elements/content-sharing/ContentSharingV2.tsx index 35d84423fa..df5f6ded36 100644 --- a/src/elements/content-sharing/ContentSharingV2.tsx +++ b/src/elements/content-sharing/ContentSharingV2.tsx @@ -28,6 +28,10 @@ import type { AvatarURLMap } from './types'; import messages from './messages'; +interface ContentSharingUser extends User { + email?: string; +} + export interface ContentSharingV2Props { /** api - API instance */ api: API; @@ -68,7 +72,7 @@ function ContentSharingV2({ const [hasError, setHasError] = React.useState(false); const [sharedLink, setSharedLink] = React.useState(null); const [sharingServiceProps, setSharingServiceProps] = React.useState(null); - const [currentUser, setCurrentUser] = React.useState(null); + const [currentUser, setCurrentUser] = React.useState(null); const [collaborationRoles, setCollaborationRoles] = React.useState(null); const [collaborators, setCollaborators] = React.useState(null); const [collaboratorsData, setCollaboratorsData] = React.useState(null); @@ -87,7 +91,7 @@ function ContentSharingV2({ api, avatarUrlMap, collaborators, - currentUserId: currentUser?.id, + currentUser, item, itemId, itemType, @@ -187,8 +191,8 @@ function ContentSharingV2({ return; } - const { enterprise, hostname, id } = userData; - setCurrentUser({ id }); + const { enterprise, hostname, id, login } = userData; + setCurrentUser({ id, email: login }); setSharingServiceProps(prevSharingServiceProps => ({ ...prevSharingServiceProps, serverUrl: hostname ? `${hostname}v/` : '', @@ -266,7 +270,7 @@ function ContentSharingV2({ if (avatarUrlMap && collaboratorsData && currentUser && owner) { const collaboratorsWithAvatars = convertCollabsResponse( collaboratorsData, - currentUser.id, + currentUser, owner, avatarUrlMap, ); diff --git a/src/elements/content-sharing/apis/__tests__/fetchCurrentUser.test.ts b/src/elements/content-sharing/apis/__tests__/fetchCurrentUser.test.ts index f0c7365f72..40df2188ca 100644 --- a/src/elements/content-sharing/apis/__tests__/fetchCurrentUser.test.ts +++ b/src/elements/content-sharing/apis/__tests__/fetchCurrentUser.test.ts @@ -1,5 +1,5 @@ import { DEFAULT_USER_API_RESPONSE, MOCK_ITEM } from '../../utils/__mocks__/ContentSharingV2Mocks'; -import { FIELD_ENTERPRISE, FIELD_HOSTNAME } from '../../../../constants'; +import { FIELD_ENTERPRISE, FIELD_HOSTNAME, FIELD_LOGIN } from '../../../../constants'; import { fetchCurrentUser } from '..'; import { createSuccessMock, createUsersApiMock } from './testUtils'; @@ -17,7 +17,7 @@ describe('content-sharing/apis/fetchCurrentUser', () => { expect(defaultApiMock.getUsersAPI).toHaveBeenCalledWith(false); expect(getDefaultUserMock).toHaveBeenCalledWith(MOCK_ITEM.id, expect.any(Function), expect.any(Function), { params: { - fields: [FIELD_ENTERPRISE, FIELD_HOSTNAME].toString(), + fields: [FIELD_ENTERPRISE, FIELD_HOSTNAME, FIELD_LOGIN].toString(), }, }); expect(result).toEqual(DEFAULT_USER_API_RESPONSE); diff --git a/src/elements/content-sharing/apis/fetchCurrentUser.ts b/src/elements/content-sharing/apis/fetchCurrentUser.ts index 069035cc6a..859e1416dc 100644 --- a/src/elements/content-sharing/apis/fetchCurrentUser.ts +++ b/src/elements/content-sharing/apis/fetchCurrentUser.ts @@ -1,6 +1,6 @@ import type { User } from '@box/unified-share-modal'; -import { FIELD_ENTERPRISE, FIELD_HOSTNAME } from '../../../constants'; +import { FIELD_ENTERPRISE, FIELD_HOSTNAME, FIELD_LOGIN } from '../../../constants'; import type { BaseFetchProps } from '../types'; @@ -8,7 +8,7 @@ export const fetchCurrentUser = async ({ api, itemId }: BaseFetchProps): Promise return new Promise((resolve, reject) => { api.getUsersAPI(false).getUser(itemId, resolve, reject, { params: { - fields: [FIELD_ENTERPRISE, FIELD_HOSTNAME].toString(), + fields: [FIELD_ENTERPRISE, FIELD_HOSTNAME, FIELD_LOGIN].toString(), }, }); }); diff --git a/src/elements/content-sharing/hooks/__tests__/useSharingService.test.ts b/src/elements/content-sharing/hooks/__tests__/useSharingService.test.ts index de48198c3b..ae6c953623 100644 --- a/src/elements/content-sharing/hooks/__tests__/useSharingService.test.ts +++ b/src/elements/content-sharing/hooks/__tests__/useSharingService.test.ts @@ -70,7 +70,7 @@ const renderHookWithProps = (props = {}) => { api: mockApi, avatarUrlMap: {}, collaborators: [], - currentUserId: '123', + currentUser: { id: '123' }, item: mockItem, itemId: mockItemId, itemType: TYPE_FILE, @@ -274,13 +274,13 @@ describe('elements/content-sharing/hooks/useSharingService', () => { describe('handleSendInvitations', () => { const mockCollaborators = [{ id: 'collab-1', email: 'existing@example.com', type: 'user' }]; const mockAvatarUrlMap = { 'user-1': 'https://example.com/avatar.jpg' }; - const mockCurrentUserId = 'current-user-123'; + const mockCurrentUser = { id: 'current-user-123' }; test('should call useInvites with correct parameters', () => { renderHookWithProps({ collaborators: mockCollaborators, avatarUrlMap: mockAvatarUrlMap, - currentUserId: mockCurrentUserId, + currentUser: mockCurrentUser, }); expect(useInvites).toHaveBeenCalledWith(mockApi, mockItemId, TYPE_FILE, { @@ -302,7 +302,7 @@ describe('elements/content-sharing/hooks/useSharingService', () => { renderHookWithProps({ collaborators: mockCollaborators, avatarUrlMap: mockAvatarUrlMap, - currentUserId: mockCurrentUserId, + currentUser: mockCurrentUser, }); // Get the handleSuccess and setCollaborators function that was passed to useInvites @@ -313,7 +313,7 @@ describe('elements/content-sharing/hooks/useSharingService', () => { expect(convertCollab).toHaveBeenCalledWith({ collab: mockResponse, - currentUserId: mockCurrentUserId, + currentUser: mockCurrentUser, isCurrentUserOwner: false, ownerEmailDomain: 'test.com', avatarUrlMap: mockAvatarUrlMap, diff --git a/src/elements/content-sharing/hooks/useSharingService.ts b/src/elements/content-sharing/hooks/useSharingService.ts index db1b26b019..c8d97f58b0 100644 --- a/src/elements/content-sharing/hooks/useSharingService.ts +++ b/src/elements/content-sharing/hooks/useSharingService.ts @@ -15,7 +15,7 @@ export const useSharingService = ({ api, avatarUrlMap, collaborators, - currentUserId, + currentUser, item, itemId, itemType, @@ -92,15 +92,15 @@ export const useSharingService = ({ const nextCollab = convertCollab({ avatarUrlMap, collab: response, - currentUserId, - isCurrentUserOwner: currentUserId === ownerId, + currentUser, + isCurrentUserOwner: currentUser.id === ownerId, ownerEmailDomain, }); return nextCollab ? [...prevCollabs, nextCollab] : prevCollabs; }); }, - [avatarUrlMap, currentUserId, setCollaborators], + [avatarUrlMap, currentUser, setCollaborators], ); const handleSendInvitations = useInvites(api, itemId, itemType, { diff --git a/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts b/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts index 553b8654f8..a3d4697c87 100644 --- a/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts +++ b/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts @@ -18,6 +18,11 @@ const ownerFromApi = { email: mockOwnerEmail, name: mockOwnerName, }; +const mockCurrentUser = { + id: mockOwnerId, + email: mockOwnerEmail, + name: mockOwnerName, +}; const itemOwner = { id: mockOwnerEmail, status: STATUS_ACCEPTED, @@ -94,7 +99,7 @@ describe('convertCollaborators', () => { const result = convertCollab({ avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[1], - currentUserId: mockOwnerId, + currentUser: mockCurrentUser, isCurrentUserOwner: false, ownerEmailDomain, }); @@ -118,7 +123,7 @@ describe('convertCollaborators', () => { const result = convertCollab({ avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[5], - currentUserId: mockOwnerId, + currentUser: mockCurrentUser, isCurrentUserOwner: true, ownerEmailDomain, }); @@ -141,7 +146,7 @@ describe('convertCollaborators', () => { const result = convertCollab({ avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[3], - currentUserId: mockOwnerId, + currentUser: mockCurrentUser, isCurrentUserOwner: false, ownerEmailDomain, }); @@ -165,7 +170,7 @@ describe('convertCollaborators', () => { const result = convertCollab({ avatarUrlMap: mockAvatarUrlMap, collab, - currentUserId: mockOwnerId, + currentUser: mockCurrentUser, isCurrentUserOwner: false, ownerEmailDomain, }); @@ -177,7 +182,7 @@ describe('convertCollaborators', () => { const result = convertCollab({ avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[4], - currentUserId: mockOwnerId, + currentUser: mockCurrentUser, isCurrentUserOwner: false, ownerEmailDomain, }); @@ -189,7 +194,7 @@ describe('convertCollaborators', () => { const result = convertCollab({ avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[6], - currentUserId: mockOwnerId, + currentUser: mockCurrentUser, isCurrentUserOwner: false, ownerEmailDomain, }); @@ -201,7 +206,7 @@ describe('convertCollaborators', () => { const result = convertCollab({ avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[0], - currentUserId: mockOwnerId, + currentUser: mockCurrentUser, isCurrentUserOwner: true, ownerEmailDomain, }); @@ -224,7 +229,7 @@ describe('convertCollaborators', () => { const result = convertCollab({ avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[2], - currentUserId: mockOwnerId, + currentUser: mockCurrentUser, isCurrentUserOwner: true, ownerEmailDomain, }); @@ -238,7 +243,7 @@ describe('convertCollaborators', () => { const result = convertCollab({ avatarUrlMap, collab: mockCollaborations[1], - currentUserId: mockOwnerId, + currentUser: mockCurrentUser, isCurrentUserOwner: false, ownerEmailDomain, }); @@ -257,7 +262,7 @@ describe('convertCollaborators', () => { const result = convertCollab({ avatarUrlMap: mockAvatarUrlMap, collab: collabWithoutExpiration, - currentUserId: mockOwnerId, + currentUser: mockCurrentUser, isCurrentUserOwner: false, ownerEmailDomain, }); @@ -270,7 +275,7 @@ describe('convertCollaborators', () => { test('should convert valid collaborations data to Collaborator array', () => { const result = convertCollabsResponse( mockCollaborationsFromApi, - mockOwnerId, + mockCurrentUser, ownerFromApi, mockAvatarUrlMap, ); @@ -346,13 +351,13 @@ describe('convertCollaborators', () => { test('should return empty array for empty entries', () => { const emptyCollaborations: Collaborations = { entries: [] }; - const result = convertCollabsResponse(emptyCollaborations, mockOwnerId, ownerFromApi, mockAvatarUrlMap); + const result = convertCollabsResponse(emptyCollaborations, mockCurrentUser, ownerFromApi, mockAvatarUrlMap); expect(result).toEqual([]); }); test('should handle null avatar URL map', () => { - const collabs = convertCollabsResponse(mockCollaborationsFromApi, mockOwnerId, ownerFromApi, null); + const collabs = convertCollabsResponse(mockCollaborationsFromApi, mockCurrentUser, ownerFromApi, null); collabs.map(collab => { expect(collab.avatarUrl).toBeUndefined(); diff --git a/src/elements/content-sharing/utils/convertCollaborators.ts b/src/elements/content-sharing/utils/convertCollaborators.ts index 51ab0f2692..6194a91764 100644 --- a/src/elements/content-sharing/utils/convertCollaborators.ts +++ b/src/elements/content-sharing/utils/convertCollaborators.ts @@ -8,12 +8,12 @@ import { USM_TO_API_COLLAB_ROLE_MAP, } from '../constants'; -import type { Collaboration, Collaborations } from '../../../common/types/core'; +import type { Collaboration, Collaborations, User } from '../../../common/types/core'; import type { AvatarURLMap } from '../types'; export interface ConvertCollabProps { collab: Collaboration; - currentUserId: string; + currentUser: User; isCurrentUserOwner: boolean; ownerEmailDomain: string; avatarUrlMap?: AvatarURLMap; @@ -22,7 +22,7 @@ export interface ConvertCollabProps { export const convertCollab = ({ avatarUrlMap, collab, - currentUserId, + currentUser, isCurrentUserOwner, ownerEmailDomain, }: ConvertCollabProps): Collaborator | null => { @@ -48,10 +48,15 @@ export const convertCollab = ({ return null; } - // External collaborator icons will only be displayed in the USM if the current user owns - // the item and if the collaborator's email domain differs from the owner's email domain. + // External collaborator icons will only be displayed in the USM if the current user is the item owner or + // belongs to the same enterprise as the owner, and if the collaborator's email domain differs from the owner's enterprise email domain. + const currentUserEmailDomain = + !!currentUser?.email && /@/.test(currentUser.email) ? currentUser.email.split('@')[1] : null; const isExternal = - isCurrentUserOwner && !!collabEmail && !!ownerEmailDomain && collabEmail.split('@')[1] !== ownerEmailDomain; + (isCurrentUserOwner || currentUserEmailDomain === ownerEmailDomain) && + !!collabEmail && + !!ownerEmailDomain && + collabEmail.split('@')[1] !== ownerEmailDomain; const avatarUrl = avatarUrlMap ? avatarUrlMap[collabId] : undefined; return { @@ -60,7 +65,7 @@ export const convertCollab = ({ expiresAt, hasCustomAvatar: !!avatarUrl, id: `${id}`, - isCurrentUser: collabId != null && collabId === currentUserId, + isCurrentUser: collabId != null && collabId === currentUser.id, isExternal, isPending: status === STATUS_PENDING, name: collabName || collabEmail, @@ -71,7 +76,7 @@ export const convertCollab = ({ export const convertCollabsResponse = ( collabsApiData: Collaborations, - currentUserId: string, + currentUser: User, owner: { id: string; email: string; name: string }, avatarUrlMap?: AvatarURLMap, ): Collaborator[] => { @@ -79,7 +84,7 @@ export const convertCollabsResponse = ( if (!entries.length) return []; const { id: ownerId, email: ownerEmail, name: ownerName } = owner; - const isCurrentUserOwner = currentUserId === ownerId; + const isCurrentUserOwner = currentUser.id === ownerId; const ownerEmailDomain = ownerEmail && /@/.test(ownerEmail) ? ownerEmail.split('@')[1] : null; const itemOwner = { @@ -94,7 +99,7 @@ export const convertCollabsResponse = ( }; return [itemOwner, ...entries].flatMap(collab => { - const converted = convertCollab({ avatarUrlMap, collab, currentUserId, isCurrentUserOwner, ownerEmailDomain }); + const converted = convertCollab({ avatarUrlMap, collab, currentUser, isCurrentUserOwner, ownerEmailDomain }); return converted ? [converted] : []; }); }; From 0f7a1230048cdd175315bcfe29d0d0aca05a8c93 Mon Sep 17 00:00:00 2001 From: reneshen0328 Date: Tue, 26 May 2026 09:57:15 -0700 Subject: [PATCH 4/7] fix: null gaurd clause --- src/elements/content-sharing/hooks/useSharingService.ts | 2 +- .../utils/__tests__/convertCollaborators.test.ts | 1 - src/elements/content-sharing/utils/convertCollaborators.ts | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/elements/content-sharing/hooks/useSharingService.ts b/src/elements/content-sharing/hooks/useSharingService.ts index c8d97f58b0..2d6f9e5c5f 100644 --- a/src/elements/content-sharing/hooks/useSharingService.ts +++ b/src/elements/content-sharing/hooks/useSharingService.ts @@ -93,7 +93,7 @@ export const useSharingService = ({ avatarUrlMap, collab: response, currentUser, - isCurrentUserOwner: currentUser.id === ownerId, + isCurrentUserOwner: currentUser?.id === ownerId, ownerEmailDomain, }); diff --git a/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts b/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts index a3d4697c87..851cb49833 100644 --- a/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts +++ b/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts @@ -21,7 +21,6 @@ const ownerFromApi = { const mockCurrentUser = { id: mockOwnerId, email: mockOwnerEmail, - name: mockOwnerName, }; const itemOwner = { id: mockOwnerEmail, diff --git a/src/elements/content-sharing/utils/convertCollaborators.ts b/src/elements/content-sharing/utils/convertCollaborators.ts index 6194a91764..f88091de07 100644 --- a/src/elements/content-sharing/utils/convertCollaborators.ts +++ b/src/elements/content-sharing/utils/convertCollaborators.ts @@ -65,7 +65,7 @@ export const convertCollab = ({ expiresAt, hasCustomAvatar: !!avatarUrl, id: `${id}`, - isCurrentUser: collabId != null && collabId === currentUser.id, + isCurrentUser: collabId != null && collabId === currentUser?.id, isExternal, isPending: status === STATUS_PENDING, name: collabName || collabEmail, @@ -84,7 +84,7 @@ export const convertCollabsResponse = ( if (!entries.length) return []; const { id: ownerId, email: ownerEmail, name: ownerName } = owner; - const isCurrentUserOwner = currentUser.id === ownerId; + const isCurrentUserOwner = currentUser?.id === ownerId; const ownerEmailDomain = ownerEmail && /@/.test(ownerEmail) ? ownerEmail.split('@')[1] : null; const itemOwner = { From c623626b10be59998b512eab1ecb0c690d34a60a Mon Sep 17 00:00:00 2001 From: reneshen0328 Date: Tue, 26 May 2026 10:29:33 -0700 Subject: [PATCH 5/7] fix: item api id type and pass login in test --- .../content-sharing/utils/__mocks__/ContentSharingV2Mocks.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/elements/content-sharing/utils/__mocks__/ContentSharingV2Mocks.js b/src/elements/content-sharing/utils/__mocks__/ContentSharingV2Mocks.js index 1333ff692d..bfd5555bee 100644 --- a/src/elements/content-sharing/utils/__mocks__/ContentSharingV2Mocks.js +++ b/src/elements/content-sharing/utils/__mocks__/ContentSharingV2Mocks.js @@ -41,7 +41,7 @@ export const mockAvatarUrlMap = { export const mockOwnerEmail = 'aotter@example.com'; export const mockOwnerName = 'Astronaut Otter'; -export const mockOwnerId = 789; +export const mockOwnerId = '789'; export const collabUser1 = { id: 456, @@ -94,6 +94,7 @@ export const EMPTY_COLLABORATIONS_RESPONSE = { export const DEFAULT_USER_API_RESPONSE = { id: '789', enterprise: { id: '12345', name: 'Test Enterprise' }, + login: mockOwner.login, }; export const FREE_USER_API_RESPONSE = { From 058056d27bbb78b4f22a5743fc201731ad573fb6 Mon Sep 17 00:00:00 2001 From: reneshen0328 Date: Tue, 26 May 2026 13:10:14 -0700 Subject: [PATCH 6/7] fix: comments --- .../content-sharing/ContentSharingV2.tsx | 10 ++--- .../hooks/__tests__/useSharingService.test.ts | 1 - .../hooks/useSharingService.ts | 3 +- src/elements/content-sharing/types.js | 7 ++- .../utils/__mocks__/ContentSharingV2Mocks.js | 1 + .../__tests__/convertCollaborators.test.ts | 45 ++++++++++--------- .../utils/convertCollaborators.ts | 17 +++---- 7 files changed, 41 insertions(+), 43 deletions(-) diff --git a/src/elements/content-sharing/ContentSharingV2.tsx b/src/elements/content-sharing/ContentSharingV2.tsx index df5f6ded36..a3fa0bc61b 100644 --- a/src/elements/content-sharing/ContentSharingV2.tsx +++ b/src/elements/content-sharing/ContentSharingV2.tsx @@ -11,7 +11,6 @@ import type { Item, SharedLink, SharingService, - User, VariantType, } from '@box/unified-share-modal'; @@ -24,14 +23,10 @@ import { convertCollabsResponse, convertItemResponse } from './utils'; import type { ElementsXhrError } from '../../common/types/api'; import type { Collaborations, ItemType } from '../../common/types/core'; -import type { AvatarURLMap } from './types'; +import type { AvatarURLMap, ContentSharingUser } from './types'; import messages from './messages'; -interface ContentSharingUser extends User { - email?: string; -} - export interface ContentSharingV2Props { /** api - API instance */ api: API; @@ -192,7 +187,8 @@ function ContentSharingV2({ } const { enterprise, hostname, id, login } = userData; - setCurrentUser({ id, email: login }); + const emailDomain = login && /@/.test(login) ? login.split('@')[1] : undefined; + setCurrentUser({ id, email: login, emailDomain }); setSharingServiceProps(prevSharingServiceProps => ({ ...prevSharingServiceProps, serverUrl: hostname ? `${hostname}v/` : '', diff --git a/src/elements/content-sharing/hooks/__tests__/useSharingService.test.ts b/src/elements/content-sharing/hooks/__tests__/useSharingService.test.ts index ae6c953623..0565aeea3c 100644 --- a/src/elements/content-sharing/hooks/__tests__/useSharingService.test.ts +++ b/src/elements/content-sharing/hooks/__tests__/useSharingService.test.ts @@ -314,7 +314,6 @@ describe('elements/content-sharing/hooks/useSharingService', () => { expect(convertCollab).toHaveBeenCalledWith({ collab: mockResponse, currentUser: mockCurrentUser, - isCurrentUserOwner: false, ownerEmailDomain: 'test.com', avatarUrlMap: mockAvatarUrlMap, }); diff --git a/src/elements/content-sharing/hooks/useSharingService.ts b/src/elements/content-sharing/hooks/useSharingService.ts index 2d6f9e5c5f..fff815ee2f 100644 --- a/src/elements/content-sharing/hooks/useSharingService.ts +++ b/src/elements/content-sharing/hooks/useSharingService.ts @@ -85,7 +85,7 @@ export const useSharingService = ({ // Create the sendInvitations callbacks using the existing memoized useInvites hook const handleSuccess = React.useCallback( response => { - const { id: ownerId, login: ownerEmail } = response.created_by; + const { login: ownerEmail } = response.created_by; const ownerEmailDomain = ownerEmail && /@/.test(ownerEmail) ? ownerEmail.split('@')[1] : null; setCollaborators(prevCollabs => { @@ -93,7 +93,6 @@ export const useSharingService = ({ avatarUrlMap, collab: response, currentUser, - isCurrentUserOwner: currentUser?.id === ownerId, ownerEmailDomain, }); diff --git a/src/elements/content-sharing/types.js b/src/elements/content-sharing/types.js index 89cd2b01d0..5e7c796536 100644 --- a/src/elements/content-sharing/types.js +++ b/src/elements/content-sharing/types.js @@ -1,5 +1,5 @@ // @flow -import type { CollaborationRole, Collaborator, DateValue, Item, SharedLink } from '@box/unified-share-modal'; +import type { CollaborationRole, Collaborator, DateValue, Item, SharedLink, User } from '@box/unified-share-modal'; import API from '../../api'; @@ -190,3 +190,8 @@ export interface SharedLinkSettings { password: string; vanityName: string; } + +export interface ContentSharingUser extends User { + email?: string; + emailDomain?: string; +} diff --git a/src/elements/content-sharing/utils/__mocks__/ContentSharingV2Mocks.js b/src/elements/content-sharing/utils/__mocks__/ContentSharingV2Mocks.js index bfd5555bee..794cb25ef1 100644 --- a/src/elements/content-sharing/utils/__mocks__/ContentSharingV2Mocks.js +++ b/src/elements/content-sharing/utils/__mocks__/ContentSharingV2Mocks.js @@ -40,6 +40,7 @@ export const mockAvatarUrlMap = { }; export const mockOwnerEmail = 'aotter@example.com'; +export const mockOwnerEmailDomain = 'example.com'; export const mockOwnerName = 'Astronaut Otter'; export const mockOwnerId = '789'; diff --git a/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts b/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts index 851cb49833..53808a26a2 100644 --- a/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts +++ b/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts @@ -7,12 +7,12 @@ import { mockAvatarUrlMap, mockOwnerId, mockOwnerEmail, + mockOwnerEmailDomain, mockOwnerName, } from '../__mocks__/ContentSharingV2Mocks'; import type { Collaborations } from '../../../../common/types/core'; -const ownerEmailDomain = 'example.com'; const ownerFromApi = { id: mockOwnerId, email: mockOwnerEmail, @@ -21,6 +21,7 @@ const ownerFromApi = { const mockCurrentUser = { id: mockOwnerId, email: mockOwnerEmail, + emailDomain: mockOwnerEmailDomain, }; const itemOwner = { id: mockOwnerEmail, @@ -99,8 +100,7 @@ describe('convertCollaborators', () => { avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[1], currentUser: mockCurrentUser, - isCurrentUserOwner: false, - ownerEmailDomain, + ownerEmailDomain: mockOwnerEmailDomain, }); expect(result).toEqual({ @@ -123,8 +123,7 @@ describe('convertCollaborators', () => { avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[5], currentUser: mockCurrentUser, - isCurrentUserOwner: true, - ownerEmailDomain, + ownerEmailDomain: mockOwnerEmailDomain, }); expect(result).toEqual({ @@ -146,8 +145,7 @@ describe('convertCollaborators', () => { avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[3], currentUser: mockCurrentUser, - isCurrentUserOwner: false, - ownerEmailDomain, + ownerEmailDomain: mockOwnerEmailDomain, }); expect(result).toEqual({ @@ -170,8 +168,7 @@ describe('convertCollaborators', () => { avatarUrlMap: mockAvatarUrlMap, collab, currentUser: mockCurrentUser, - isCurrentUserOwner: false, - ownerEmailDomain, + ownerEmailDomain: mockOwnerEmailDomain, }); expect(result).toBeNull(); @@ -182,8 +179,7 @@ describe('convertCollaborators', () => { avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[4], currentUser: mockCurrentUser, - isCurrentUserOwner: false, - ownerEmailDomain, + ownerEmailDomain: mockOwnerEmailDomain, }); expect(result).toBeNull(); @@ -194,8 +190,7 @@ describe('convertCollaborators', () => { avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[6], currentUser: mockCurrentUser, - isCurrentUserOwner: false, - ownerEmailDomain, + ownerEmailDomain: mockOwnerEmailDomain, }); expect(result).toBeNull(); @@ -206,8 +201,7 @@ describe('convertCollaborators', () => { avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[0], currentUser: mockCurrentUser, - isCurrentUserOwner: true, - ownerEmailDomain, + ownerEmailDomain: mockOwnerEmailDomain, }); expect(result).toEqual({ @@ -229,13 +223,24 @@ describe('convertCollaborators', () => { avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[2], currentUser: mockCurrentUser, - isCurrentUserOwner: true, - ownerEmailDomain, + ownerEmailDomain: mockOwnerEmailDomain, }); expect(result.isExternal).toBe(true); }); + test('should not mark collaborator as external when currentUser has no emailDomain', () => { + const currentUserWithoutDomain = { id: mockOwnerId, email: mockOwnerEmail }; + const result = convertCollab({ + avatarUrlMap: mockAvatarUrlMap, + collab: mockCollaborations[2], + currentUser: currentUserWithoutDomain, + ownerEmailDomain: mockOwnerEmailDomain, + }); + + expect(result.isExternal).toBe(false); + }); + test.each([null, undefined, {}, { 999: 'https://example.com/different-user-avatar.jpg' }])( 'should handle %s avatar URL map', avatarUrlMap => { @@ -243,8 +248,7 @@ describe('convertCollaborators', () => { avatarUrlMap, collab: mockCollaborations[1], currentUser: mockCurrentUser, - isCurrentUserOwner: false, - ownerEmailDomain, + ownerEmailDomain: mockOwnerEmailDomain, }); expect(result.avatarUrl).toBeUndefined(); @@ -262,8 +266,7 @@ describe('convertCollaborators', () => { avatarUrlMap: mockAvatarUrlMap, collab: collabWithoutExpiration, currentUser: mockCurrentUser, - isCurrentUserOwner: false, - ownerEmailDomain, + ownerEmailDomain: mockOwnerEmailDomain, }); expect(result.expiresAt).toBeNull(); diff --git a/src/elements/content-sharing/utils/convertCollaborators.ts b/src/elements/content-sharing/utils/convertCollaborators.ts index f88091de07..e043778757 100644 --- a/src/elements/content-sharing/utils/convertCollaborators.ts +++ b/src/elements/content-sharing/utils/convertCollaborators.ts @@ -8,13 +8,12 @@ import { USM_TO_API_COLLAB_ROLE_MAP, } from '../constants'; -import type { Collaboration, Collaborations, User } from '../../../common/types/core'; -import type { AvatarURLMap } from '../types'; +import type { Collaboration, Collaborations } from '../../../common/types/core'; +import type { AvatarURLMap, ContentSharingUser } from '../types'; export interface ConvertCollabProps { collab: Collaboration; - currentUser: User; - isCurrentUserOwner: boolean; + currentUser: ContentSharingUser; ownerEmailDomain: string; avatarUrlMap?: AvatarURLMap; } @@ -23,7 +22,6 @@ export const convertCollab = ({ avatarUrlMap, collab, currentUser, - isCurrentUserOwner, ownerEmailDomain, }: ConvertCollabProps): Collaborator | null => { if (!collab || collab.status === STATUS_REJECTED) { @@ -50,10 +48,8 @@ export const convertCollab = ({ // External collaborator icons will only be displayed in the USM if the current user is the item owner or // belongs to the same enterprise as the owner, and if the collaborator's email domain differs from the owner's enterprise email domain. - const currentUserEmailDomain = - !!currentUser?.email && /@/.test(currentUser.email) ? currentUser.email.split('@')[1] : null; const isExternal = - (isCurrentUserOwner || currentUserEmailDomain === ownerEmailDomain) && + currentUser?.emailDomain === ownerEmailDomain && !!collabEmail && !!ownerEmailDomain && collabEmail.split('@')[1] !== ownerEmailDomain; @@ -76,7 +72,7 @@ export const convertCollab = ({ export const convertCollabsResponse = ( collabsApiData: Collaborations, - currentUser: User, + currentUser: ContentSharingUser, owner: { id: string; email: string; name: string }, avatarUrlMap?: AvatarURLMap, ): Collaborator[] => { @@ -84,7 +80,6 @@ export const convertCollabsResponse = ( if (!entries.length) return []; const { id: ownerId, email: ownerEmail, name: ownerName } = owner; - const isCurrentUserOwner = currentUser?.id === ownerId; const ownerEmailDomain = ownerEmail && /@/.test(ownerEmail) ? ownerEmail.split('@')[1] : null; const itemOwner = { @@ -99,7 +94,7 @@ export const convertCollabsResponse = ( }; return [itemOwner, ...entries].flatMap(collab => { - const converted = convertCollab({ avatarUrlMap, collab, currentUser, isCurrentUserOwner, ownerEmailDomain }); + const converted = convertCollab({ avatarUrlMap, collab, currentUser, ownerEmailDomain }); return converted ? [converted] : []; }); }; From 247a01187505577dd96c7e559b6483bb43460bc5 Mon Sep 17 00:00:00 2001 From: reneshen0328 Date: Tue, 26 May 2026 14:05:16 -0700 Subject: [PATCH 7/7] fix: update comment --- src/elements/content-sharing/utils/convertCollaborators.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/elements/content-sharing/utils/convertCollaborators.ts b/src/elements/content-sharing/utils/convertCollaborators.ts index e043778757..8e061dc8c0 100644 --- a/src/elements/content-sharing/utils/convertCollaborators.ts +++ b/src/elements/content-sharing/utils/convertCollaborators.ts @@ -46,8 +46,8 @@ export const convertCollab = ({ return null; } - // External collaborator icons will only be displayed in the USM if the current user is the item owner or - // belongs to the same enterprise as the owner, and if the collaborator's email domain differs from the owner's enterprise email domain. + // The external badge on the collaborator avatar is only visible if the current user is + // in the same enterprise as the item owner and the collaborator is not in the same enterprise. const isExternal = currentUser?.emailDomain === ownerEmailDomain && !!collabEmail &&