diff --git a/package-lock.json b/package-lock.json index 1f1edaa86..b0633b9c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@material-ui/lab": "^4.0.0-alpha.61", "@material-ui/styles": "^4.11.4", "@reach/auto-id": "^0.16.0", - "@saleor/macaw-ui": "^0.8.0-pre.1", + "@saleor/macaw-ui": "^0.8.0-pre.4", "@saleor/sdk": "^0.4.4", "@sentry/react": "^6.0.0", "@types/faker": "^5.1.6", @@ -7235,9 +7235,9 @@ } }, "node_modules/@saleor/macaw-ui": { - "version": "0.8.0-pre.1", - "resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.1.tgz", - "integrity": "sha512-zNnLJcm13HMqGJjHmjyoLL/lQ5dIefUbOq/Ws7wxPn1Bh0wwhY/3T+Qc6iG5W9gvIcpTXJojwL7tT31DHQ2KRw==", + "version": "0.8.0-pre.4", + "resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.4.tgz", + "integrity": "sha512-9x97IhjCxZ5FD0b3pJfNs2U3YEvsnlJWZHXvGZKPztr0XbtkGbMy5EdHazSH/lNeI3g2T9CE9uKONyatiXAvmw==", "engines": { "node": ">=16 <19" }, @@ -44660,9 +44660,9 @@ } }, "@saleor/macaw-ui": { - "version": "0.8.0-pre.1", - "resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.1.tgz", - "integrity": "sha512-zNnLJcm13HMqGJjHmjyoLL/lQ5dIefUbOq/Ws7wxPn1Bh0wwhY/3T+Qc6iG5W9gvIcpTXJojwL7tT31DHQ2KRw==" + "version": "0.8.0-pre.4", + "resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.4.tgz", + "integrity": "sha512-9x97IhjCxZ5FD0b3pJfNs2U3YEvsnlJWZHXvGZKPztr0XbtkGbMy5EdHazSH/lNeI3g2T9CE9uKONyatiXAvmw==" }, "@saleor/sdk": { "version": "0.4.4", diff --git a/package.json b/package.json index 8cf576c99..700c886dc 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@editorjs/list": "^1.7.0", "@editorjs/paragraph": "^2.8.0", "@editorjs/quote": "^2.4.0", + "@floating-ui/react-dom-interactions": "^0.5.0", "@glideapps/glide-data-grid": "^5.0.0", "@graphiql/plugin-explorer": "^0.1.12", "@graphiql/react": "^0.15.0", @@ -33,9 +34,8 @@ "@material-ui/lab": "^4.0.0-alpha.61", "@material-ui/styles": "^4.11.4", "@reach/auto-id": "^0.16.0", - "@saleor/macaw-ui": "^0.8.0-pre.1", + "@saleor/macaw-ui": "^0.8.0-pre.4", "@saleor/sdk": "^0.4.4", - "@floating-ui/react-dom-interactions": "^0.5.0", "@sentry/react": "^6.0.0", "@types/faker": "^5.1.6", "@uiw/react-color-hue": "0.0.34", @@ -204,6 +204,7 @@ "husky": "^8.0.0", "jest": "^27.5.1", "jest-canvas-mock": "^2.4.0", + "jest-environment-jsdom": "^27.5.1", "jest-file": "^1.0.0", "jest-localstorage-mock": "^2.4.3", "lint-staged": "^10.5.1", @@ -213,8 +214,7 @@ "mochawesome-report-generator": "^6.0.1", "prettier": "^2.8.3", "setup-polly-jest": "^0.9.1", - "ts-jest": "^27.1.5", - "jest-environment-jsdom": "^27.5.1" + "ts-jest": "^27.1.5" }, "jest": { "resetMocks": false, diff --git a/src/components/Sidebar/NewSidebar.tsx b/src/components/Sidebar/NewSidebar.tsx index 5bcf81efa..4c1ffb77f 100644 --- a/src/components/Sidebar/NewSidebar.tsx +++ b/src/components/Sidebar/NewSidebar.tsx @@ -1,3 +1,42 @@ +import { useUser } from "@dashboard/auth"; +import { Box, List, sprinkles, Text } from "@saleor/macaw-ui/next"; import React from "react"; +import { useIntl } from "react-intl"; +import { Link } from "react-router-dom"; -export const NewSidebar = () =>
Work in progress
; +import useMenuStructure from "./useMenuStructure"; + +export const NewSidebar = () => { + const intl = useIntl(); + const { user } = useUser(); + const [menuStructure] = useMenuStructure(intl, user); + + return ( + + + {menuStructure.map(menuItem => ( + + + + {menuItem.icon} + {menuItem.label} + + + + ))} + + + ); +}; diff --git a/src/components/Sidebar/useMenuStructure.tsx b/src/components/Sidebar/useMenuStructure.tsx new file mode 100644 index 000000000..d4da676fb --- /dev/null +++ b/src/components/Sidebar/useMenuStructure.tsx @@ -0,0 +1,292 @@ +import { appsListPath } from "@dashboard/apps/urls"; +import { + extensionMountPoints, + useExtensions, +} from "@dashboard/apps/useExtensions"; +import { categoryListUrl } from "@dashboard/categories/urls"; +import { collectionListUrl } from "@dashboard/collections/urls"; +import { MARKETPLACE_URL } from "@dashboard/config"; +import { configurationMenuUrl } from "@dashboard/configuration"; +import { getConfigMenuItemsPermissions } from "@dashboard/configuration/utils"; +import { customerListUrl } from "@dashboard/customers/urls"; +import { saleListUrl, voucherListUrl } from "@dashboard/discounts/urls"; +import { giftCardListUrl } from "@dashboard/giftCards/urls"; +import { PermissionEnum, UserFragment } from "@dashboard/graphql"; +import { commonMessages, sectionNames } from "@dashboard/intl"; +import { marketplaceUrlResolver } from "@dashboard/marketplace/marketplace-url-resolver"; +import { orderDraftListUrl, orderListUrl } from "@dashboard/orders/urls"; +import { pageListPath } from "@dashboard/pages/urls"; +import { productListUrl } from "@dashboard/products/urls"; +import { languageListUrl } from "@dashboard/translations/urls"; +import { SidebarMenuItem } from "@saleor/macaw-ui"; +import { + ConfigurationIcon, + CustomersIcon, + HomeIcon, + MarketplaceIcon, + OrdersIcon, + ProductsIcons, + TableEditIcon, + TranslationsIcon, + VouchersIcon, +} from "@saleor/macaw-ui/next"; +import React from "react"; +import { IntlShape } from "react-intl"; + +import { getMenuItemExtension, mapToExtensionsItems } from "./legacy/utils"; + +export interface FilterableMenuItem extends Omit { + children?: FilterableMenuItem[]; + permissions?: PermissionEnum[]; +} + +function useMenuStructure(intl: IntlShape, user: UserFragment) { + const extensions = useExtensions(extensionMountPoints.NAVIGATION_SIDEBAR); + + const handleMenuItemClick = (menuItem: SidebarMenuItem) => { + const extension = getMenuItemExtension(extensions, menuItem); + if (extension) { + extension.open(); + return; + } + }; + + const appExtensionsHeaderItem = { + id: "extensions", + label: intl.formatMessage(sectionNames.appExtensions), + }; + + // This will be deleted when Marketplace is released + // Consider this solution as temporary + const getAppSection = () => { + if (MARKETPLACE_URL) { + return { + icon: , + label: intl.formatMessage(sectionNames.apps), + permissions: [PermissionEnum.MANAGE_APPS], + id: "apps_section", + children: [ + { + label: intl.formatMessage(sectionNames.apps), + id: "apps", + url: appsListPath, + }, + { + label: intl.formatMessage(sectionNames.marketplace), + id: "marketplace-saleor-apps", + url: marketplaceUrlResolver.getSaleorAppsDashboardPath(), + }, + { + ariaLabel: "marketplace", + label: intl.formatMessage(sectionNames.appTemplateGallery), + id: "marketplace-template-gallery", + url: marketplaceUrlResolver.getTemplateGalleryDashboardPath(), + }, + ], + }; + } + + return { + icon: , + label: intl.formatMessage(sectionNames.apps), + permissions: [PermissionEnum.MANAGE_APPS], + id: "apps", + url: appsListPath, + }; + }; + + const menuItems = [ + { + icon: , + label: intl.formatMessage(sectionNames.home), + id: "home", + url: "/", + }, + { + children: [ + { + label: intl.formatMessage(sectionNames.products), + id: "products", + url: productListUrl(), + permissions: [PermissionEnum.MANAGE_PRODUCTS], + }, + { + label: intl.formatMessage(sectionNames.categories), + id: "categories", + url: categoryListUrl(), + permissions: [PermissionEnum.MANAGE_PRODUCTS], + }, + { + label: intl.formatMessage(sectionNames.collections), + id: "collections", + url: collectionListUrl(), + permissions: [PermissionEnum.MANAGE_PRODUCTS], + }, + { + label: intl.formatMessage(sectionNames.giftCards), + id: "giftCards", + url: giftCardListUrl(), + permissions: [PermissionEnum.MANAGE_GIFT_CARD], + }, + ...mapToExtensionsItems( + extensions.NAVIGATION_CATALOG, + appExtensionsHeaderItem, + ), + ], + icon: , + label: intl.formatMessage(commonMessages.catalog), + permissions: [ + PermissionEnum.MANAGE_GIFT_CARD, + PermissionEnum.MANAGE_PRODUCTS, + ], + id: "catalogue", + }, + { + children: [ + { + label: intl.formatMessage(sectionNames.orders), + permissions: [PermissionEnum.MANAGE_ORDERS], + id: "orders", + url: orderListUrl(), + }, + { + label: intl.formatMessage(commonMessages.drafts), + permissions: [PermissionEnum.MANAGE_ORDERS], + id: "order-drafts", + url: orderDraftListUrl(), + }, + ...mapToExtensionsItems( + extensions.NAVIGATION_ORDERS, + appExtensionsHeaderItem, + ), + ], + icon: , + label: intl.formatMessage(sectionNames.orders), + permissions: [PermissionEnum.MANAGE_ORDERS], + id: "orders", + }, + { + children: extensions.NAVIGATION_CUSTOMERS.length > 0 && [ + { + label: intl.formatMessage(sectionNames.customers), + permissions: [PermissionEnum.MANAGE_USERS], + id: "customers", + url: customerListUrl(), + }, + ...mapToExtensionsItems( + extensions.NAVIGATION_CUSTOMERS, + appExtensionsHeaderItem, + ), + ], + icon: , + label: intl.formatMessage(sectionNames.customers), + permissions: [PermissionEnum.MANAGE_USERS], + id: "customers", + url: customerListUrl(), + }, + + { + children: [ + { + label: intl.formatMessage(sectionNames.sales), + id: "sales", + url: saleListUrl(), + }, + { + label: intl.formatMessage(sectionNames.vouchers), + id: "vouchers", + url: voucherListUrl(), + }, + ...mapToExtensionsItems( + extensions.NAVIGATION_DISCOUNTS, + appExtensionsHeaderItem, + ), + ], + icon: , + label: intl.formatMessage(commonMessages.discounts), + permissions: [PermissionEnum.MANAGE_DISCOUNTS], + id: "discounts", + }, + { + children: extensions.NAVIGATION_PAGES.length > 0 && [ + { + label: intl.formatMessage(sectionNames.pages), + permissions: [PermissionEnum.MANAGE_PAGES], + id: "pages", + url: pageListPath, + }, + ...mapToExtensionsItems( + extensions.NAVIGATION_PAGES, + appExtensionsHeaderItem, + ), + ], + icon: , + label: intl.formatMessage(sectionNames.pages), + permissions: [PermissionEnum.MANAGE_PAGES], + id: "pages", + url: pageListPath, + }, + getAppSection(), + { + children: extensions.NAVIGATION_TRANSLATIONS.length > 0 && [ + { + label: intl.formatMessage(sectionNames.translations), + permissions: [PermissionEnum.MANAGE_TRANSLATIONS], + id: "translations", + url: languageListUrl, + }, + ...mapToExtensionsItems( + extensions.NAVIGATION_TRANSLATIONS, + appExtensionsHeaderItem, + ), + ], + icon: , + label: intl.formatMessage(sectionNames.translations), + permissions: [PermissionEnum.MANAGE_TRANSLATIONS], + id: "translations", + url: languageListUrl, + }, + { + icon: , + label: intl.formatMessage(sectionNames.configuration), + permissions: getConfigMenuItemsPermissions(intl), + id: "configure", + url: configurationMenuUrl, + }, + ]; + + const isMenuItemPermitted = (menuItem: FilterableMenuItem) => { + const userPermissions = (user?.userPermissions || []).map( + permission => permission.code, + ); + if (!menuItem?.permissions || menuItem?.permissions?.length < 1) { + return true; + } + return menuItem.permissions.some(permission => + userPermissions.includes(permission), + ); + }; + + const getFilteredMenuItems = (menuItems: FilterableMenuItem[]) => + menuItems.filter(isMenuItemPermitted); + + return [ + menuItems.reduce( + (resultItems: FilterableMenuItem[], menuItem: FilterableMenuItem) => { + if (!isMenuItemPermitted(menuItem)) { + return resultItems; + } + const { children } = menuItem; + const filteredChildren = children + ? getFilteredMenuItems(children) + : undefined; + + return [...resultItems, { ...menuItem, children: filteredChildren }]; + }, + [], + ), + handleMenuItemClick, + ] as const; +} + +export default useMenuStructure;