-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1343 from vivid-planet/feature/user-permissions
[Feature] User permissions
- Loading branch information
Showing
86 changed files
with
1,103 additions
and
1,044 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@comet/cms-admin": minor | ||
--- | ||
|
||
Add `MasterMenu` and `MasterMenuRoutes` components which both take a single data structure to define menu and routes. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
--- | ||
"@comet/cms-api": major | ||
--- | ||
|
||
Replace ContentScopeModule with UserPermissionsModule | ||
|
||
Breaking changes: | ||
|
||
- ContentScope-Module has been removed | ||
- canAccessScope has been moved to AccessControlService and renamed to isAllowedContentScope | ||
- role- and rights-fields has been removed from CurrentUser-Object | ||
- AllowForRole-decorator has been removed | ||
- Rename decorator SubjectEntity to AffectedEntity | ||
- Add RequiredPermission-decorator and make it mandatory when using UserPermissionsModule | ||
|
||
Upgrade-Guide: tbd |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,95 +1,203 @@ | ||
import { Menu, MenuCollapsibleItem, MenuContext, MenuItemRouterLink, useWindowSize } from "@comet/admin"; | ||
import { Assets, Dashboard, Data, PageTree, Snips, Wrench } from "@comet/admin-icons"; | ||
import { Assets, Dashboard as DashboardIcon, Data, PageTree, Snips, Wrench } from "@comet/admin-icons"; | ||
import { | ||
createRedirectsPage, | ||
CronJobsPage, | ||
DamPage, | ||
MasterMenu as CometMasterMenu, | ||
MasterMenuData, | ||
PagesPage, | ||
PublisherPage, | ||
UserPermissionsPage, | ||
} from "@comet/cms-admin"; | ||
import Dashboard from "@src/dashboard/Dashboard"; | ||
import { GQLPageTreeNodeCategory } from "@src/graphql.generated"; | ||
import { Link } from "@src/links/Link"; | ||
import { NewsLinkBlock } from "@src/news/blocks/NewsLinkBlock"; | ||
import { NewsPage } from "@src/news/generated/NewsPage"; | ||
import MainMenu from "@src/pages/mainMenu/MainMenu"; | ||
import { Page } from "@src/pages/Page"; | ||
import { categoryToUrlParam, pageTreeCategories, urlParamToCategory } from "@src/pageTree/pageTreeCategories"; | ||
import { PredefinedPage } from "@src/predefinedPage/PredefinedPage"; | ||
import ProductCategoriesPage from "@src/products/categories/ProductCategoriesPage"; | ||
import { ProductsPage } from "@src/products/generated/ProductsPage"; | ||
import ProductsHandmadePage from "@src/products/ProductsPage"; | ||
import ProductTagsPage from "@src/products/tags/ProductTagsPage"; | ||
import * as React from "react"; | ||
import { useIntl } from "react-intl"; | ||
import { useRouteMatch } from "react-router"; | ||
import { FormattedMessage } from "react-intl"; | ||
import { Redirect, RouteComponentProps } from "react-router-dom"; | ||
|
||
const permanentMenuMinWidth = 1024; | ||
import { ComponentDemo } from "./ComponentDemo"; | ||
import { ContentScopeIndicator } from "./ContentScopeIndicator"; | ||
import { EditPageNode } from "./EditPageNode"; | ||
|
||
const MasterMenu: React.FC = () => { | ||
const { open, toggleOpen } = React.useContext(MenuContext); | ||
const windowSize = useWindowSize(); | ||
const intl = useIntl(); | ||
const match = useRouteMatch(); | ||
export const pageTreeDocumentTypes = { | ||
Page, | ||
Link, | ||
PredefinedPage, | ||
}; | ||
|
||
const useTemporaryMenu: boolean = windowSize.width < permanentMenuMinWidth; | ||
const RedirectsPage = createRedirectsPage({ customTargets: { news: NewsLinkBlock }, scopeParts: ["domain"] }); | ||
|
||
// Open menu when changing to permanent variant and close when changing to temporary variant. | ||
React.useEffect(() => { | ||
if ((useTemporaryMenu && open) || (!useTemporaryMenu && !open)) { | ||
toggleOpen(); | ||
} | ||
// useEffect dependencies must only include `location`, because the function should only be called once after changing the location. | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [location]); | ||
export const masterMenuData: MasterMenuData = [ | ||
{ | ||
primary: <FormattedMessage id="menu.dashboard" defaultMessage="Dashboard" />, | ||
icon: <DashboardIcon />, | ||
route: { | ||
path: "/dashboard", | ||
component: Dashboard, | ||
}, | ||
}, | ||
{ | ||
primary: <FormattedMessage id="menu.pageTree" defaultMessage="Page tree" />, | ||
icon: <PageTree />, | ||
submenu: pageTreeCategories.map((category) => ({ | ||
primary: category.label, | ||
to: `/pages/pagetree/${categoryToUrlParam(category.category as GQLPageTreeNodeCategory)}`, | ||
})), | ||
route: { | ||
path: "/pages/pagetree/:category", | ||
render: ({ match }: RouteComponentProps<{ category: string }>) => { | ||
const category = urlParamToCategory(match.params.category); | ||
|
||
return ( | ||
<Menu variant={useTemporaryMenu ? "temporary" : "permanent"}> | ||
<MenuItemRouterLink | ||
primary={intl.formatMessage({ id: "menu.dashboard", defaultMessage: "Dashboard" })} | ||
icon={<Dashboard />} | ||
to={`${match.url}/dashboard`} | ||
/> | ||
<MenuCollapsibleItem primary={intl.formatMessage({ id: "menu.pageTree", defaultMessage: "Page tree" })} icon={<PageTree />}> | ||
<MenuItemRouterLink | ||
primary={intl.formatMessage({ id: "menu.pageTree.mainNavigation", defaultMessage: "Main navigation" })} | ||
to={`${match.url}/pages/pagetree/main-navigation`} | ||
/> | ||
<MenuItemRouterLink | ||
primary={intl.formatMessage({ id: "menu.pageTree.topMenu", defaultMessage: "Top menu" })} | ||
to={`${match.url}/pages/pagetree/top-menu`} | ||
/> | ||
</MenuCollapsibleItem> | ||
if (category === undefined) { | ||
return <Redirect to={`${match.url}/dashboard`} />; | ||
} | ||
|
||
<MenuCollapsibleItem primary={intl.formatMessage({ id: "menu.structuredContent", defaultMessage: "Structured Content" })} icon={<Data />}> | ||
<MenuItemRouterLink | ||
primary={intl.formatMessage({ id: "menu.news", defaultMessage: "News" })} | ||
to={`${match.url}/structured-content/news`} | ||
/> | ||
</MenuCollapsibleItem> | ||
<MenuItemRouterLink | ||
primary={intl.formatMessage({ id: "menu.dam", defaultMessage: "Assets" })} | ||
icon={<Assets />} | ||
to={`${match.url}/assets`} | ||
/> | ||
<MenuCollapsibleItem primary={intl.formatMessage({ id: "menu.projectSnips", defaultMessage: "Project snips" })} icon={<Snips />}> | ||
<MenuItemRouterLink | ||
primary={intl.formatMessage({ id: "menu.mainMenu", defaultMessage: "Main menu" })} | ||
to={`${match.url}/project-snips/main-menu`} | ||
/> | ||
</MenuCollapsibleItem> | ||
<MenuCollapsibleItem primary={intl.formatMessage({ id: "menu.system", defaultMessage: "System" })} icon={<Wrench />}> | ||
<MenuItemRouterLink | ||
primary={intl.formatMessage({ id: "menu.publisher", defaultMessage: "Publisher" })} | ||
to={`${match.url}/system/publisher`} | ||
/> | ||
<MenuItemRouterLink | ||
primary={intl.formatMessage({ id: "menu.cronJobs", defaultMessage: "Cron Jobs" })} | ||
to={`${match.url}/system/cron-jobs`} | ||
/> | ||
<MenuItemRouterLink | ||
primary={intl.formatMessage({ id: "menu.redirects", defaultMessage: "Redirects" })} | ||
to={`${match.url}/system/redirects`} | ||
/> | ||
</MenuCollapsibleItem> | ||
<MenuItemRouterLink | ||
primary={intl.formatMessage({ id: "menu.componentDemo", defaultMessage: "Component demo" })} | ||
to={`${match.url}/component-demo`} | ||
icon={<Snips />} | ||
/> | ||
<MenuCollapsibleItem primary="Products" icon={<Snips />}> | ||
<MenuItemRouterLink primary="Products" to={`${match.url}/products`} icon={<Snips />} /> | ||
<MenuItemRouterLink primary="Categories" to={`${match.url}/product-categories`} icon={<Snips />} /> | ||
<MenuItemRouterLink primary="Tags" to={`${match.url}/product-tags`} icon={<Snips />} /> | ||
<MenuItemRouterLink primary="Products Handmade" to={`${match.url}/products-handmade`} icon={<Snips />} /> | ||
</MenuCollapsibleItem> | ||
<MenuItemRouterLink | ||
primary={intl.formatMessage({ id: "menu.userPermissions", defaultMessage: "User Permissions" })} | ||
to={`${match.url}/user-permissions`} | ||
icon={<Snips />} | ||
/> | ||
</Menu> | ||
); | ||
}; | ||
return ( | ||
<PagesPage | ||
path={`/pages/pagetree/${match.params.category}`} | ||
allCategories={pageTreeCategories} | ||
documentTypes={pageTreeDocumentTypes} | ||
editPageNode={EditPageNode} | ||
category={category} | ||
renderContentScopeIndicator={(scope) => <ContentScopeIndicator scope={scope} variant="toolbar" />} | ||
/> | ||
); | ||
}, | ||
}, | ||
requiredPermission: "pageTree", | ||
}, | ||
{ | ||
primary: <FormattedMessage id="menu.structuredContent" defaultMessage="Structured Content" />, | ||
icon: <Data />, | ||
submenu: [ | ||
{ | ||
primary: <FormattedMessage id="menu.news" defaultMessage="News" />, | ||
route: { | ||
path: "/structured-content/news", | ||
component: NewsPage, | ||
}, | ||
}, | ||
], | ||
requiredPermission: "news", | ||
}, | ||
{ | ||
primary: <FormattedMessage id="menu.dam" defaultMessage="Assets" />, | ||
icon: <Assets />, | ||
route: { | ||
path: "/assets", | ||
render: () => <DamPage renderContentScopeIndicator={(scope) => <ContentScopeIndicator scope={scope} domainOnly variant="toolbar" />} />, | ||
}, | ||
requiredPermission: "dam", | ||
}, | ||
{ | ||
primary: <FormattedMessage id="menu.projectSnips" defaultMessage="Project snips" />, | ||
icon: <Snips />, | ||
submenu: [ | ||
{ | ||
primary: <FormattedMessage id="menu.mainMenu" defaultMessage="Main menu" />, | ||
route: { | ||
path: "/project-snips/main-menu", | ||
component: MainMenu, | ||
}, | ||
}, | ||
], | ||
requiredPermission: "pageTree", | ||
}, | ||
{ | ||
primary: <FormattedMessage id="menu.system" defaultMessage="System" />, | ||
icon: <Wrench />, | ||
submenu: [ | ||
{ | ||
primary: <FormattedMessage id="menu.publisher" defaultMessage="Publisher" />, | ||
route: { | ||
path: "/system/publisher", | ||
component: PublisherPage, | ||
}, | ||
requiredPermission: "builds", | ||
}, | ||
{ | ||
primary: <FormattedMessage id="menu.cronJobs" defaultMessage="Cron Jobs" />, | ||
route: { | ||
path: "/system/cron-jobs", | ||
component: CronJobsPage, | ||
}, | ||
requiredPermission: "cronJobs", | ||
}, | ||
{ | ||
primary: <FormattedMessage id="menu.redirects" defaultMessage="Redirects" />, | ||
route: { | ||
path: "/system/redirects", | ||
render: () => <RedirectsPage redirectPathAfterChange="/system/redirects" />, | ||
}, | ||
requiredPermission: "pageTree", | ||
}, | ||
], | ||
}, | ||
{ | ||
primary: <FormattedMessage id="menu.componentDemo" defaultMessage="Component demo" />, | ||
icon: <Snips />, | ||
route: { | ||
path: "/component-demo", | ||
component: ComponentDemo, | ||
}, | ||
requiredPermission: "pageTree", | ||
}, | ||
{ | ||
primary: <FormattedMessage id="menu.products" defaultMessage="Products" />, | ||
icon: <Snips />, | ||
submenu: [ | ||
{ | ||
primary: <FormattedMessage id="menu.products" defaultMessage="Products" />, | ||
route: { | ||
path: "/products", | ||
component: ProductsPage, | ||
}, | ||
}, | ||
{ | ||
primary: <FormattedMessage id="menu.productCategories" defaultMessage="Categories" />, | ||
route: { | ||
path: "/product-categories", | ||
component: ProductCategoriesPage, | ||
}, | ||
}, | ||
{ | ||
primary: <FormattedMessage id="menu.productTags" defaultMessage="Tags" />, | ||
route: { | ||
path: "/product-tags", | ||
component: ProductTagsPage, | ||
}, | ||
}, | ||
{ | ||
primary: <FormattedMessage id="menu.productsHandmade" defaultMessage="Products Handmade" />, | ||
route: { | ||
path: "/products-handmade", | ||
component: ProductsHandmadePage, | ||
}, | ||
}, | ||
], | ||
requiredPermission: "products", | ||
}, | ||
{ | ||
primary: <FormattedMessage id="menu.userPermissions" defaultMessage="User Permissions" />, | ||
icon: <Snips />, | ||
route: { | ||
path: "/user-permissions", | ||
component: UserPermissionsPage, | ||
}, | ||
requiredPermission: "userPermissions", | ||
}, | ||
]; | ||
|
||
const MasterMenu = () => <CometMasterMenu menu={masterMenuData} />; | ||
export default MasterMenu; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.