diff --git a/package.json b/package.json index c697ed2388..2eacd1c3d9 100644 --- a/package.json +++ b/package.json @@ -124,14 +124,14 @@ "@babel/preset-typescript": "^7.24.7", "@babel/template": "^7.24.7", "@babel/types": "^7.24.7", - "@box/blueprint-web": "^12.138.0", - "@box/blueprint-web-assets": "^4.101.7", + "@box/blueprint-web": "^13.5.12", + "@box/blueprint-web-assets": "^4.104.7", "@box/box-ai-agent-selector": "^0.53.0", "@box/box-ai-content-answers": "^0.139.0", "@box/box-item-type-selector": "^0.73.1", "@box/cldr-data": "^34.2.0", "@box/combobox-with-api": "^1.32.1", - "@box/copy-input": "^1.27.4", + "@box/copy-input": "^1.39.17", "@box/frontend": "^11.0.1", "@box/item-icon": "^0.27.1", "@box/languages": "^1.0.0", @@ -140,8 +140,8 @@ "@box/metadata-view": "^1.10.0", "@box/react-virtualized": "^9.22.3-rc-box.10", "@box/types": "^0.2.1", - "@box/unified-share-modal": "^1.45.0", - "@box/user-selector": "^1.60.4", + "@box/unified-share-modal": "^2.7.10", + "@box/user-selector": "^1.73.18", "@cfaester/enzyme-adapter-react-18": "^0.8.0", "@chromatic-com/storybook": "^4.0.1", "@commitlint/cli": "^19.8.0", @@ -295,22 +295,22 @@ "webpack-dev-server": "^5.2.1" }, "peerDependencies": { - "@box/blueprint-web": "^12.138.0", - "@box/blueprint-web-assets": "^4.101.7", + "@box/blueprint-web": "^13.5.12", + "@box/blueprint-web-assets": "^4.104.7", "@box/box-ai-agent-selector": "^0.53.0", "@box/box-ai-content-answers": "^0.139.0", "@box/box-item-type-selector": "^0.73.1", "@box/cldr-data": ">=34.2.0", "@box/combobox-with-api": "^1.18.0", - "@box/copy-input": "^1.27.4", + "@box/copy-input": "^1.39.17", "@box/item-icon": "^0.27.1", "@box/metadata-editor": "^1.38.0", "@box/metadata-filter": "^1.41.3", "@box/metadata-view": "^1.10.0", "@box/react-virtualized": "^9.22.3-rc-box.10", "@box/types": "^0.2.1", - "@box/unified-share-modal": "^1.45.0", - "@box/user-selector": "^1.60.4", + "@box/unified-share-modal": "^2.7.10", + "@box/user-selector": "^1.73.18", "@hapi/address": "^2.1.4", "@tanstack/react-virtual": "^3.13.12", "axios": "^0.30.0", diff --git a/src/elements/content-sharing/ContentSharingV2.tsx b/src/elements/content-sharing/ContentSharingV2.tsx index 8dfdcf2f71..ef857cd6df 100644 --- a/src/elements/content-sharing/ContentSharingV2.tsx +++ b/src/elements/content-sharing/ContentSharingV2.tsx @@ -1,6 +1,6 @@ +import isEmpty from 'lodash/isEmpty'; import * as React from 'react'; import { useIntl } from 'react-intl'; -import isEmpty from 'lodash/isEmpty'; import { useNotification } from '@box/blueprint-web'; import { UnifiedShareModal } from '@box/unified-share-modal'; @@ -38,11 +38,24 @@ export interface ContentSharingV2Props { itemId: string; /** itemType - "file" or "folder" */ itemType: ItemType; + /** onClose - Callback when the modal is closed by user action */ + onClose?: () => void; + /** onError - Callback when item data fails to load, preventing USM from opening */ + onError?: (error: ElementsXhrError) => void; /** variant - "desktop" or "modal" variant of the Unified Share Modal */ variant?: VariantType; } -function ContentSharingV2({ api, children, config: usmConfig, itemId, itemType, variant }: ContentSharingV2Props) { +function ContentSharingV2({ + api, + children, + config: usmConfig, + itemId, + itemType, + onClose, + onError, + variant, +}: ContentSharingV2Props) { const [avatarUrlMap, setAvatarUrlMap] = React.useState(null); const [item, setItem] = React.useState(null); const [hasError, setHasError] = React.useState(false); @@ -52,7 +65,7 @@ function ContentSharingV2({ api, children, config: usmConfig, itemId, itemType, const [collaborationRoles, setCollaborationRoles] = React.useState(null); const [collaborators, setCollaborators] = React.useState(null); const [collaboratorsData, setCollaboratorsData] = React.useState(null); - const [owner, setOwner] = React.useState({ id: '', email: '', name: '' }); + const [owner, setOwner] = React.useState({ email: '', id: '', name: '' }); const { formatMessage } = useIntl(); const { addNotification } = useNotification(); @@ -86,7 +99,7 @@ function ContentSharingV2({ api, children, config: usmConfig, itemId, itemType, setSharedLink(sharedLinkFromApi); setSharingServiceProps(sharingServicePropsFromApi); setCollaborationRoles(collaborationRolesFromApi); - setOwner({ id: ownedBy.id, email: ownedBy.login, name: ownedBy.name }); + setOwner({ email: ownedBy.login, id: ownedBy.id, name: ownedBy.name }); }, []); // Handle initial data retrieval errors @@ -114,12 +127,12 @@ function ContentSharingV2({ api, children, config: usmConfig, itemId, itemType, addNotification({ closeButtonAriaLabel: formatMessage(messages.noticeCloseLabel), sensitivity: 'foreground' as const, + styledText: formatMessage(errorMessage), typeIconAriaLabel: formatMessage(messages.errorNoticeIcon), variant: 'error', - styledText: formatMessage(errorMessage), }); }, - [hasError, addNotification, formatMessage], + [addNotification, formatMessage, hasError], ); // Reset state if the API has changed @@ -144,19 +157,20 @@ function ContentSharingV2({ api, children, config: usmConfig, itemId, itemType, handleGetItemSuccess(itemData); } catch (error) { getError(error); + onError?.(error); } })(); - }, [api, item, itemId, itemType, sharedLink, handleGetItemSuccess, getError]); + }, [api, item, itemId, itemType, sharedLink, getError, handleGetItemSuccess, onError]); // Get current user React.useEffect(() => { if (!api || isEmpty(api) || !item || currentUser) return; const getUserSuccess = userData => { - const { id, enterprise, hostname } = userData; + const { enterprise, hostname, id } = userData; setCurrentUser({ - id, enterprise: { name: enterprise ? enterprise.name : '' }, + id, }); setSharingServiceProps(prevSharingServiceProps => ({ ...prevSharingServiceProps, @@ -222,6 +236,12 @@ function ContentSharingV2({ api, children, config: usmConfig, itemId, itemType, const config = React.useMemo(() => ({ sharedLinkEmail: false, ...usmConfig }), [usmConfig]); + const handleOpenChange = (open: boolean) => { + if (!open) { + onClose?.(); + } + }; + return ( item && ( { }); }); }); + + describe('callback props', () => { + const onError = jest.fn(); + const onClose = jest.fn(); + + const error = { status: 400 }; + const errorApi = { + ...defaultApiMock, + getFileAPI: jest.fn().mockReturnValue({ + getFile: jest.fn().mockImplementation((id, successFn, errorFn) => { + errorFn(error); + }), + }), + }; + + test('should call onError when item data fails to load', async () => { + renderComponent({ api: errorApi, onError }); + + await waitFor(() => { + expect(onError).toHaveBeenCalledWith(error); + }); + }); + + test('should call onClose when modal is closed', async () => { + renderComponent({ onClose }); + expect(await screen.findByRole('heading', { name: 'Share ‘Box Development Guide.pdf’' })).toBeVisible(); + + const closeButton = screen.getByRole('button', { name: 'Close' }); + closeButton.click(); + expect(onClose).toHaveBeenCalled(); + }); + }); }); 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 0a0b77f429..cd877b3bc9 100644 --- a/src/elements/content-sharing/stories/tests/ContentSharingV2-visual.stories.tsx +++ b/src/elements/content-sharing/stories/tests/ContentSharingV2-visual.stories.tsx @@ -49,8 +49,7 @@ export const DesktopVariant = { api: mockApiWithSharedLink, variant: 'desktop', }, - play: async context => { - await withModernization.play(context); + play: async () => { expect(screen.queryByRole('button', { name: 'Close' })).not.toBeInTheDocument(); }, }; diff --git a/src/elements/content-sidebar/__tests__/__snapshots__/SidebarFileProperties.test.js.snap b/src/elements/content-sidebar/__tests__/__snapshots__/SidebarFileProperties.test.js.snap index 0ac5a664e0..91e696f948 100644 --- a/src/elements/content-sidebar/__tests__/__snapshots__/SidebarFileProperties.test.js.snap +++ b/src/elements/content-sidebar/__tests__/__snapshots__/SidebarFileProperties.test.js.snap @@ -130,7 +130,7 @@ exports[`elements/content-sidebar/SidebarFileProperties render() should render r

diff --git a/yarn.lock b/yarn.lock index cf31f26d01..e9bc65c21e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1419,19 +1419,19 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@box/blueprint-web-assets@^4.101.7": - version "4.101.7" - resolved "https://registry.yarnpkg.com/@box/blueprint-web-assets/-/blueprint-web-assets-4.101.7.tgz#f539533020b51336268f9c154b3f5dbb82cd4f88" - integrity sha512-5UdaCZFImeBb9YXb/Ri/EQSRVBcsbMaRxEHIDkaUFjcfjkg8CjtI29kqwqrtYGmwa2ueuicf5qfSRmreaJ/gfQ== +"@box/blueprint-web-assets@^4.104.7": + version "4.104.7" + resolved "https://registry.yarnpkg.com/@box/blueprint-web-assets/-/blueprint-web-assets-4.104.7.tgz#45ce699aaba5e20226444112bab31eb1ca003035" + integrity sha512-RFrWEnjz/s1QzLuAZasLUSiGaRwGPxaM/zUM91lyl/5jRytGpY65xym5+jKQXtGihmzwxkoIb/Q4Pl/npLFOAA== -"@box/blueprint-web@^12.138.0": - version "12.138.0" - resolved "https://registry.yarnpkg.com/@box/blueprint-web/-/blueprint-web-12.138.0.tgz#8abe2c2fba19b4a5c7fb5ef4b22019b3e1a0d8b2" - integrity sha512-qNxeP6gewELITn5NC/X6EknfXdwRmrnyAWYeEb8hPrF9F0zZeGZRzYsjTutsM1cdQRbVdh+PX9OyBo/qD2vsnw== +"@box/blueprint-web@^13.5.12": + version "13.5.12" + resolved "https://registry.yarnpkg.com/@box/blueprint-web/-/blueprint-web-13.5.12.tgz#d50929563deae3d1f043cca16caa13ea267f647e" + integrity sha512-rjsScSPXr8+9Uu2oQBCgHwoVkFBFTGCvOVMPnJ9JD0OekVF24ZyAg0aW6Yo0xlRnkcDz+26As0M9obB6thmcFA== dependencies: "@ariakit/react" "0.4.15" "@ariakit/react-core" "0.4.15" - "@box/blueprint-web-assets" "^4.101.7" + "@box/blueprint-web-assets" "^4.104.7" "@internationalized/date" "^3.7.0" "@radix-ui/react-accordion" "1.1.2" "@radix-ui/react-checkbox" "1.0.4" @@ -1488,10 +1488,10 @@ "@box/tree" "^1.29.1" react-accessible-treeview "2.9.0" -"@box/copy-input@^1.27.4": - version "1.27.5" - resolved "https://registry.yarnpkg.com/@box/copy-input/-/copy-input-1.27.5.tgz#db84e2f95c3c4029470ce98c05d2601694f7abe0" - integrity sha512-9vXfBS1znmOWmrl/FxLVtAHpyK9Cgpt+d6abFLpLWwcduls20cfQmGbxSWGKVo6YDxeoLBQqsKSOTqilvuyOqQ== +"@box/copy-input@^1.39.17": + version "1.39.17" + resolved "https://registry.yarnpkg.com/@box/copy-input/-/copy-input-1.39.17.tgz#52ae6d53ac1b70edc7fd82b475ef4eb1ec5ca37f" + integrity sha512-Wgjp2Y6LQ7gVTHWm5E8WDovo5HKVcAQ7NaUA5SfMas8lOdYSJ808r1Ogi6ZGWOeSWB+JAUbP5d1R//2+bxWnSQ== "@box/frontend@^11.0.1": version "11.0.1" @@ -1549,15 +1549,15 @@ resolved "https://registry.yarnpkg.com/@box/types/-/types-0.2.1.tgz#cd0a3915b2306e4cf581f6091b95f5d2db75ea60" integrity sha512-wd6nRR9QxBl7lYKJ/Hix0AKg1PNC3leZWOJ9Nt+d4j45WxCYBiCemZAtY2ekL5BITpVw8vlLmquzSpPhDTeO5A== -"@box/unified-share-modal@^1.45.0": - version "1.45.0" - resolved "https://registry.yarnpkg.com/@box/unified-share-modal/-/unified-share-modal-1.45.0.tgz#2e668a10959119b27023fd66a9213f89f6aae9e2" - integrity sha512-ZMWKyNQHIEsk/biUWTTz+R+AsmjPrXmaMRT2pUMOWR5PSAPR9FANIUYTZ1wVEozzgU8ynsIajpTdtPF8d2plGg== +"@box/unified-share-modal@^2.7.10": + version "2.7.10" + resolved "https://registry.yarnpkg.com/@box/unified-share-modal/-/unified-share-modal-2.7.10.tgz#3eacb8aa5269fe8c5d1aed4b0f7e90b44ee24005" + integrity sha512-+xNAQfkeA4Yyizw+svfWVxbYg2hmF2FuDStlsWvX5BaUuhMo/Kd+jCL7/mUDqwhiPP65k4eNNzGjnXnlHGiVUA== -"@box/user-selector@^1.60.4": - version "1.60.5" - resolved "https://registry.yarnpkg.com/@box/user-selector/-/user-selector-1.60.5.tgz#2f2b087ff2758f2dc5c4317f5016ff7911d7d980" - integrity sha512-flndxCgumwWYkmG3Zuz17EQ5OEYCy3cds/uMN9mEr/zRGhK7SJUBjZpv0FeYSHdRCIQIFHFPg3PutBRhxuGa5w== +"@box/user-selector@^1.73.18": + version "1.73.18" + resolved "https://registry.yarnpkg.com/@box/user-selector/-/user-selector-1.73.18.tgz#b282ff0e71a11a0d106f12bdc025fe60f49ae44e" + integrity sha512-hN+k1fYOpQfmw2dWnMRSGDTK4skNqFHTxS0uv0wjFUmon0VbEBBLbmljB7E8BSstdjO0BxNdLmY2TYgJ17fSWA== "@bundled-es-modules/cookie@^2.0.1": version "2.0.1"