From 83274e3d75e193badba4463d97d8f0137b91fd14 Mon Sep 17 00:00:00 2001 From: Rene Shen <96209918+reneshen0328@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:15:21 -0700 Subject: [PATCH 1/4] feat(content-sharing): Display pending collab (#4509) * feat(content-sharing): Display pending collab * fix: improve collab code --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- i18n/en-US.properties | 6 + src/common/types/core.js | 1 + src/constants.js | 9 +- .../__tests__/convertCollaborators.test.ts | 122 +++++++++++++++++- .../utils/convertCollaborators.ts | 40 ++++-- 5 files changed, 155 insertions(+), 23 deletions(-) diff --git a/i18n/en-US.properties b/i18n/en-US.properties index 7aac5a77c1..cf137ff5e9 100644 --- a/i18n/en-US.properties +++ b/i18n/en-US.properties @@ -962,6 +962,12 @@ boxui.accessStats.accessStatsPreviews = Previews boxui.accessStats.accessStatsViewDetails = View Details # The label for the view category of access stats for box notes boxui.accessStats.accessStatsViews = Views +# Description text about advanced content insights. +boxui.advancedContentInsights.advancedContentInsightsDescription = Get actionable insights into how viewers are engaging with this content. Measure average time spent, page-by-page engagement, per person and per visit details. +# Advanced Content Insights toggle title +boxui.advancedContentInsights.advancedContentInsightsTitleDisabled = Activate Engagement Insights +# Advanced Content Insights toggle title +boxui.advancedContentInsights.advancedContentInsightsTitleEnabled = Engagement Insights is activated # Text for an icon aria label to convey an external user of the enterprise boxui.avatar.externalUser = External user # Text for the beta badge diff --git a/src/common/types/core.js b/src/common/types/core.js index a1b4f47572..6d61696d2b 100644 --- a/src/common/types/core.js +++ b/src/common/types/core.js @@ -446,6 +446,7 @@ type AccessibleByUserOrGroup = { type CollaborationOptions = { expires_at: string | null, id: number | string, + invite_email?: string, role: string, status?: string, }; diff --git a/src/constants.js b/src/constants.js index 20f15a444f..0f8a4b8777 100644 --- a/src/constants.js +++ b/src/constants.js @@ -246,13 +246,14 @@ export const CLIENT_NAME_FOLDER_PICKER: 'FolderPicker' = 'FolderPicker'; export const CLIENT_VERSION = __VERSION__; /* ---------------------- Statuses -------------------------- */ -export const STATUS_PENDING: 'pending' = 'pending'; -export const STATUS_IN_PROGRESS: 'inprogress' = 'inprogress'; -export const STATUS_STAGED: 'staged' = 'staged'; +export const STATUS_ACCEPTED: 'accepted' = 'accepted'; export const STATUS_COMPLETE: 'complete' = 'complete'; export const STATUS_ERROR: 'error' = 'error'; -export const STATUS_ACCEPTED: 'accepted' = 'accepted'; export const STATUS_INACTIVE: 'inactive' = 'inactive'; +export const STATUS_IN_PROGRESS: 'inprogress' = 'inprogress'; +export const STATUS_PENDING: 'pending' = 'pending'; +export const STATUS_REJECTED: 'rejected' = 'rejected'; +export const STATUS_STAGED: 'staged' = 'staged'; /* ------------------- Styles ------------------------ */ export const CLASS_MODAL_CONTENT = 'be-modal-dialog-content'; diff --git a/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts b/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts index 22ae7175cc..fccb6ec13f 100644 --- a/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts +++ b/src/elements/content-sharing/utils/__tests__/convertCollaborators.test.ts @@ -1,4 +1,4 @@ -import { STATUS_ACCEPTED } from '../../../../constants'; +import { STATUS_ACCEPTED, STATUS_PENDING, STATUS_REJECTED } from '../../../../constants'; import { convertCollab, convertCollabsResponse, convertCollabsRequest } from '../convertCollaborators'; import { collabUser1, @@ -50,11 +50,39 @@ const mockCollaborationsFromApi: Collaborations = { { id: '125', role: 'editor', - status: 'pending', + status: STATUS_PENDING, expires_at: '2024-12-31T23:59:59Z', accessible_by: collabUser3, created_by: ownerFromApi, }, + { + id: '126', + role: 'editor', + status: STATUS_PENDING, + expires_at: '2024-12-31T23:59:59Z', + accessible_by: { + id: '126', + }, + created_by: ownerFromApi, + }, + { + id: '127', + role: 'editor', + status: STATUS_PENDING, + expires_at: '2024-12-31T23:59:59Z', + accessible_by: null, + created_by: ownerFromApi, + invite_email: 'bbear@external.example.com', + }, + { + id: '128', + role: 'editor', + status: STATUS_REJECTED, + expires_at: '2024-12-31T23:59:59Z', + accessible_by: null, + created_by: ownerFromApi, + invite_email: 'rrobot@external.example.com', + }, ], }; @@ -86,7 +114,30 @@ describe('convertCollaborators', () => { }); }); - test('should return null for collaboration with non-accepted status', () => { + test('should convert pending collaboration with invite_email to a pending collaborator', () => { + const result = convertCollab({ + avatarUrlMap: mockAvatarUrlMap, + collab: mockCollaborations[5], + currentUserId: mockOwnerId, + isCurrentUserOwner: false, + ownerEmailDomain, + }); + + expect(result).toEqual({ + avatarUrl: undefined, + email: 'bbear@external.example.com', + expiresAt: '2024-12-31T23:59:59Z', + hasCustomAvatar: false, + id: '127', + isCurrentUser: false, + isExternal: true, + isPending: true, + name: 'bbear@external.example.com', + role: 'editor', + }); + }); + + test('should convert pending collaboration with accessible_by', () => { const result = convertCollab({ avatarUrlMap: mockAvatarUrlMap, collab: mockCollaborations[3], @@ -95,7 +146,19 @@ describe('convertCollaborators', () => { ownerEmailDomain, }); - expect(result).toBeNull(); + expect(result).toEqual({ + avatarUrl: undefined, + email: 'dpenguin@example.com', + expiresAt: '2024-12-31T23:59:59Z', + hasCustomAvatar: false, + id: '125', + isCurrentUser: false, + isExternal: false, + isPending: true, + name: 'Dancing Penguin', + role: 'editor', + userId: '458', + }); }); test.each([undefined, null])('should return null for %s collaboration', collab => { @@ -110,6 +173,30 @@ describe('convertCollaborators', () => { expect(result).toBeNull(); }); + test('should return null for pending collab with accessible_by without collab name', () => { + const result = convertCollab({ + avatarUrlMap: mockAvatarUrlMap, + collab: mockCollaborations[4], + currentUserId: mockOwnerId, + isCurrentUserOwner: false, + ownerEmailDomain, + }); + + expect(result).toBeNull(); + }); + + test('should return null for rejected collab', () => { + const result = convertCollab({ + avatarUrlMap: mockAvatarUrlMap, + collab: mockCollaborations[6], + currentUserId: mockOwnerId, + isCurrentUserOwner: false, + ownerEmailDomain, + }); + + expect(result).toBeNull(); + }); + test('should identify current user correctly', () => { const result = convertCollab({ avatarUrlMap: mockAvatarUrlMap, @@ -188,7 +275,7 @@ describe('convertCollaborators', () => { mockAvatarUrlMap, ); - expect(result).toHaveLength(3); // Only accepted collaborations + expect(result).toHaveLength(5); // Owner + collaborators (rejected and unnamed pending omitted) expect(result).toEqual([ { avatarUrl: undefined, @@ -229,6 +316,31 @@ describe('convertCollaborators', () => { role: 'viewer', userId: '457', }, + { + avatarUrl: undefined, + email: 'dpenguin@example.com', + expiresAt: '2024-12-31T23:59:59Z', + hasCustomAvatar: false, + id: '125', + isCurrentUser: false, + isExternal: false, + isPending: true, + name: 'Dancing Penguin', + role: 'editor', + userId: '458', + }, + { + avatarUrl: undefined, + email: 'bbear@external.example.com', + expiresAt: '2024-12-31T23:59:59Z', + hasCustomAvatar: false, + id: '127', + isCurrentUser: false, + isExternal: false, + isPending: true, + name: 'bbear@external.example.com', + role: 'editor', + }, ]); }); diff --git a/src/elements/content-sharing/utils/convertCollaborators.ts b/src/elements/content-sharing/utils/convertCollaborators.ts index ccb8488f3d..1331378e0b 100644 --- a/src/elements/content-sharing/utils/convertCollaborators.ts +++ b/src/elements/content-sharing/utils/convertCollaborators.ts @@ -1,6 +1,6 @@ import { Collaborator } from '@box/unified-share-modal'; -import { INVITEE_ROLE_OWNER, STATUS_ACCEPTED } from '../../../constants'; +import { INVITEE_ROLE_OWNER, STATUS_ACCEPTED, STATUS_PENDING, STATUS_REJECTED } from '../../../constants'; import { API_TO_USM_COLLAB_ROLE_MAP, COLLAB_USER_TYPE, @@ -26,32 +26,44 @@ export const convertCollab = ({ isCurrentUserOwner, ownerEmailDomain, }: ConvertCollabProps): Collaborator | null => { - if (!collab || collab.status !== STATUS_ACCEPTED) return null; + if (!collab || collab.status === STATUS_REJECTED) { + return null; + } + + const { accessible_by: accessibleBy, id, invite_email: inviteEmail, expires_at: expiresAt, role, status } = collab; + + let collabId; + let collabEmail = inviteEmail; + let collabName = inviteEmail; + + if (accessibleBy?.name) { + const { id: userId, login, name } = accessibleBy; - const { - accessible_by: { id: collabId, login: collabEmail, name: collabName }, - id, - expires_at: executeAt, - role, - } = collab; + collabId = userId; + collabEmail = login; + collabName = name; + } + + if (!collabName) { + return null; + } - const isCurrentUser = collabId === currentUserId; const isExternal = - !isCurrentUserOwner && collabEmail && ownerEmailDomain && collabEmail.split('@')[1] !== ownerEmailDomain; + !isCurrentUserOwner && !!collabEmail && !!ownerEmailDomain && collabEmail.split('@')[1] !== ownerEmailDomain; const avatarUrl = avatarUrlMap ? avatarUrlMap[collabId] : undefined; return { avatarUrl, email: collabEmail, - expiresAt: executeAt, + expiresAt, hasCustomAvatar: !!avatarUrl, id: `${id}`, - isCurrentUser, + isCurrentUser: collabId != null && collabId === currentUserId, isExternal, - isPending: false, + isPending: status === STATUS_PENDING, name: collabName, role: API_TO_USM_COLLAB_ROLE_MAP[role], - userId: `${collabId}`, + ...(collabId != null ? { userId: `${collabId}` } : {}), }; }; From 18522fc0eadce24e12831b5042cb60ae39e14395 Mon Sep 17 00:00:00 2001 From: Duncan Hsu Date: Mon, 27 Apr 2026 16:25:29 -0700 Subject: [PATCH 2/4] fix(create-task-modal): migrate badge to bp --- .../npm/@box/blueprint-web-assets_vx.x.x.js | 1 + i18n/en-US.properties | 6 ----- src/components/checkbox/Checkbox.scss | 9 ------- src/components/checkbox/CheckboxTooltip.tsx | 26 +++++++------------ 4 files changed, 10 insertions(+), 32 deletions(-) diff --git a/flow-typed/npm/@box/blueprint-web-assets_vx.x.x.js b/flow-typed/npm/@box/blueprint-web-assets_vx.x.x.js index 9ae1fa21df..136e6a7877 100644 --- a/flow-typed/npm/@box/blueprint-web-assets_vx.x.x.js +++ b/flow-typed/npm/@box/blueprint-web-assets_vx.x.x.js @@ -16,6 +16,7 @@ declare module '@box/blueprint-web-assets/icons/Medium' { declare export var MagicWand: React$ComponentType; declare export var DocGen: React$ComponentType; declare export var Pencil: React$ComponentType; + declare export var InfoBadge: React$ComponentType; } declare module '@box/blueprint-web-assets/icons/MediumFilled' { diff --git a/i18n/en-US.properties b/i18n/en-US.properties index cf137ff5e9..7aac5a77c1 100644 --- a/i18n/en-US.properties +++ b/i18n/en-US.properties @@ -962,12 +962,6 @@ boxui.accessStats.accessStatsPreviews = Previews boxui.accessStats.accessStatsViewDetails = View Details # The label for the view category of access stats for box notes boxui.accessStats.accessStatsViews = Views -# Description text about advanced content insights. -boxui.advancedContentInsights.advancedContentInsightsDescription = Get actionable insights into how viewers are engaging with this content. Measure average time spent, page-by-page engagement, per person and per visit details. -# Advanced Content Insights toggle title -boxui.advancedContentInsights.advancedContentInsightsTitleDisabled = Activate Engagement Insights -# Advanced Content Insights toggle title -boxui.advancedContentInsights.advancedContentInsightsTitleEnabled = Engagement Insights is activated # Text for an icon aria label to convey an external user of the enterprise boxui.avatar.externalUser = External user # Text for the beta badge diff --git a/src/components/checkbox/Checkbox.scss b/src/components/checkbox/Checkbox.scss index 1fa3bc63ae..e6ac70170a 100644 --- a/src/components/checkbox/Checkbox.scss +++ b/src/components/checkbox/Checkbox.scss @@ -78,16 +78,7 @@ line-height: 0.1; // This keeps the tooltip wrapper height consistent with the child element > .info-tooltip { - position: relative; - display: inline-block; - flex: none; - width: 16px; - height: 16px; margin-left: 5px; - - > svg path { - fill: $primary-color; - } } } diff --git a/src/components/checkbox/CheckboxTooltip.tsx b/src/components/checkbox/CheckboxTooltip.tsx index 78b7de6969..4fffaf600f 100644 --- a/src/components/checkbox/CheckboxTooltip.tsx +++ b/src/components/checkbox/CheckboxTooltip.tsx @@ -1,17 +1,9 @@ import * as React from 'react'; -import { defineMessages, FormattedMessage } from 'react-intl'; import { Focusable, Tooltip, TooltipProvider } from '@box/blueprint-web'; +import { InfoBadge } from '@box/blueprint-web-assets/icons/Fill'; -import IconInfo from '../../icons/general/IconInfo'; - -const messages = defineMessages({ - checkboxTooltipIconInfoText: { - defaultMessage: 'Info', - description: 'Icon to display more information on the checkbox', - id: 'boxui.checkboxTooltip.iconInfoText', - }, -}); +import { bdlBoxBlue } from '../../styles/variables'; export interface CheckboxTooltipProps { tooltip: string; @@ -22,13 +14,13 @@ const CheckboxTooltip = ({ tooltip }: CheckboxTooltipProps) => ( -
- } - width={16} - /> -
+
From d1f336064f28f1c4fb0632f34dc5eebf22dcb0ad Mon Sep 17 00:00:00 2001 From: Rene Shen <96209918+reneshen0328@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:15:21 -0700 Subject: [PATCH 3/4] feat(content-sharing): Display pending collab (#4509) * feat(content-sharing): Display pending collab * fix: improve collab code --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- i18n/en-US.properties | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/i18n/en-US.properties b/i18n/en-US.properties index bad83b1c60..faf806c7f9 100644 --- a/i18n/en-US.properties +++ b/i18n/en-US.properties @@ -958,6 +958,12 @@ boxui.accessStats.accessStatsPreviews = Previews boxui.accessStats.accessStatsViewDetails = View Details # The label for the view category of access stats for box notes boxui.accessStats.accessStatsViews = Views +# Description text about advanced content insights. +boxui.advancedContentInsights.advancedContentInsightsDescription = Get actionable insights into how viewers are engaging with this content. Measure average time spent, page-by-page engagement, per person and per visit details. +# Advanced Content Insights toggle title +boxui.advancedContentInsights.advancedContentInsightsTitleDisabled = Activate Engagement Insights +# Advanced Content Insights toggle title +boxui.advancedContentInsights.advancedContentInsightsTitleEnabled = Engagement Insights is activated # Text for an icon aria label to convey an external user of the enterprise boxui.avatar.externalUser = External user # Text for the beta badge From 67a654f5a75ed4d97b4a2e32b119783dbfb22502 Mon Sep 17 00:00:00 2001 From: Duncan Hsu Date: Mon, 27 Apr 2026 17:54:55 -0700 Subject: [PATCH 4/4] fix(create-task-modal): fix import --- flow-typed/npm/@box/blueprint-web-assets_vx.x.x.js | 2 +- i18n/en-US.properties | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/flow-typed/npm/@box/blueprint-web-assets_vx.x.x.js b/flow-typed/npm/@box/blueprint-web-assets_vx.x.x.js index 136e6a7877..38e843cb1f 100644 --- a/flow-typed/npm/@box/blueprint-web-assets_vx.x.x.js +++ b/flow-typed/npm/@box/blueprint-web-assets_vx.x.x.js @@ -16,7 +16,6 @@ declare module '@box/blueprint-web-assets/icons/Medium' { declare export var MagicWand: React$ComponentType; declare export var DocGen: React$ComponentType; declare export var Pencil: React$ComponentType; - declare export var InfoBadge: React$ComponentType; } declare module '@box/blueprint-web-assets/icons/MediumFilled' { @@ -47,6 +46,7 @@ declare module '@box/blueprint-web-assets/icons/Fill' { declare export var Ellipsis: React$ComponentType; declare export var PointerChevronLeft: React$ComponentType; declare export var ArrowsExpand: React$ComponentType; + declare export var InfoBadge: React$ComponentType; } declare module '@box/blueprint-web-assets/icons/Logo' { diff --git a/i18n/en-US.properties b/i18n/en-US.properties index faf806c7f9..02ff43864e 100644 --- a/i18n/en-US.properties +++ b/i18n/en-US.properties @@ -958,12 +958,6 @@ boxui.accessStats.accessStatsPreviews = Previews boxui.accessStats.accessStatsViewDetails = View Details # The label for the view category of access stats for box notes boxui.accessStats.accessStatsViews = Views -# Description text about advanced content insights. -boxui.advancedContentInsights.advancedContentInsightsDescription = Get actionable insights into how viewers are engaging with this content. Measure average time spent, page-by-page engagement, per person and per visit details. -# Advanced Content Insights toggle title -boxui.advancedContentInsights.advancedContentInsightsTitleDisabled = Activate Engagement Insights -# Advanced Content Insights toggle title -boxui.advancedContentInsights.advancedContentInsightsTitleEnabled = Engagement Insights is activated # Text for an icon aria label to convey an external user of the enterprise boxui.avatar.externalUser = External user # Text for the beta badge @@ -980,8 +974,6 @@ boxui.base.previousMonth = Previous Month boxui.breadcrumb.breadcrumbLabel = Breadcrumb # Button label for the "more" dropdown menu boxui.categorySelector.label.more = More -# Icon to display more information on the checkbox -boxui.checkboxTooltip.iconInfoText = Info # Button to add classification on an item boxui.classification.add = Add # Title of the card that shows the reason why the AI classification was applied when no date is available.