feat: add admin.hidden to collections and globals (#2487)
This commit is contained in:
@@ -63,18 +63,19 @@ You can find an assortment of [example collection configs](https://github.com/pa
|
|||||||
|
|
||||||
You can customize the way that the Admin panel behaves on a collection-by-collection basis by defining the `admin` property on a collection's config.
|
You can customize the way that the Admin panel behaves on a collection-by-collection basis by defining the `admin` property on a collection's config.
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| --------------------------- | -------------|
|
|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `group` | Text used as a label for grouping collection links together in the navigation. |
|
| `group` | Text used as a label for grouping collection links together in the navigation. |
|
||||||
| `hooks` | Admin-specific hooks for this collection. [More](#admin-hooks) |
|
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this collection from navigation and admin routing. |
|
||||||
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin panel. If no field is defined, the ID of the document is used as the title. |
|
| `hooks` | Admin-specific hooks for this collection. [More](#admin-hooks) |
|
||||||
| `description` | Text or React component to display below the Collection label in the List view to give editors more information. |
|
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin panel. If no field is defined, the ID of the document is used as the title. |
|
||||||
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this collection's List view. |
|
| `description` | Text or React component to display below the Collection label in the List view to give editors more information. |
|
||||||
| `disableDuplicate ` | Disables the "Duplicate" button while editing documents within this collection. |
|
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this collection's List view. |
|
||||||
|
| `disableDuplicate ` | Disables the "Duplicate" button while editing documents within this collection. |
|
||||||
| `enableRichTextRelationship` | The [Rich Text](/docs/fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
|
| `enableRichTextRelationship` | The [Rich Text](/docs/fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
|
||||||
| `preview` | Function to generate preview URLS within the Admin panel that can point to your app. [More](#preview). |
|
| `preview` | Function to generate preview URLS within the Admin panel that can point to your app. [More](#preview). |
|
||||||
| `components` | Swap in your own React components to be used within this collection. [More](/docs/admin/components#collections) |
|
| `components` | Swap in your own React components to be used within this collection. [More](/docs/admin/components#collections) |
|
||||||
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More](#list-searchable-fields) |
|
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More](#list-searchable-fields) |
|
||||||
|
|
||||||
### Preview
|
### Preview
|
||||||
|
|
||||||
|
|||||||
@@ -64,10 +64,11 @@ You can find an [example Global config](https://github.com/payloadcms/public-dem
|
|||||||
|
|
||||||
You can customize the way that the Admin panel behaves on a Global-by-Global basis by defining the `admin` property on a Global's config.
|
You can customize the way that the Admin panel behaves on a Global-by-Global basis by defining the `admin` property on a Global's config.
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| ------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
|--------------|-----------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `components` | Swap in your own React components to be used within this Global. [More](/docs/admin/components#globals) |
|
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this global from navigation and admin routing. |
|
||||||
| `preview` | Function to generate a preview URL within the Admin panel for this global that can point to your app. [More](#preview). |
|
| `components` | Swap in your own React components to be used within this Global. [More](/docs/admin/components#globals) |
|
||||||
|
| `preview` | Function to generate a preview URL within the Admin panel for this global that can point to your app. [More](#preview). |
|
||||||
|
|
||||||
### Preview
|
### Preview
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import React, { Suspense, lazy, useState, useEffect } from 'react';
|
import React, { lazy, Suspense, useEffect, useState } from 'react';
|
||||||
import {
|
import { Redirect, Route, Switch, withRouter } from 'react-router-dom';
|
||||||
Route, Switch, withRouter, Redirect,
|
|
||||||
} from 'react-router-dom';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAuth } from './utilities/Auth';
|
import { useAuth } from './utilities/Auth';
|
||||||
import { useConfig } from './utilities/Config';
|
import { useConfig } from './utilities/Config';
|
||||||
@@ -178,82 +176,19 @@ const Routes = () => {
|
|||||||
</DocumentInfoProvider>
|
</DocumentInfoProvider>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{collections.reduce((collectionRoutes, collection) => {
|
{collections
|
||||||
const routesToReturn = [
|
.filter(({ admin: { hidden } }) => !(typeof hidden === 'function' ? hidden({ user }) : hidden))
|
||||||
...collectionRoutes,
|
.reduce((collectionRoutes, collection) => {
|
||||||
<Route
|
const routesToReturn = [
|
||||||
key={`${collection.slug}-list`}
|
...collectionRoutes,
|
||||||
path={`${match.url}/collections/${collection.slug}`}
|
|
||||||
exact
|
|
||||||
render={(routeProps) => {
|
|
||||||
if (permissions?.collections?.[collection.slug]?.read?.permission) {
|
|
||||||
return (
|
|
||||||
<List
|
|
||||||
{...routeProps}
|
|
||||||
collection={collection}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Unauthorized />;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
<Route
|
|
||||||
key={`${collection.slug}-create`}
|
|
||||||
path={`${match.url}/collections/${collection.slug}/create`}
|
|
||||||
exact
|
|
||||||
render={(routeProps) => {
|
|
||||||
if (permissions?.collections?.[collection.slug]?.create?.permission) {
|
|
||||||
return (
|
|
||||||
<DocumentInfoProvider collection={collection}>
|
|
||||||
<Edit
|
|
||||||
{...routeProps}
|
|
||||||
collection={collection}
|
|
||||||
/>
|
|
||||||
</DocumentInfoProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Unauthorized />;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
<Route
|
|
||||||
key={`${collection.slug}-edit`}
|
|
||||||
path={`${match.url}/collections/${collection.slug}/:id`}
|
|
||||||
exact
|
|
||||||
render={(routeProps) => {
|
|
||||||
const { match: { params: { id } } } = routeProps;
|
|
||||||
if (permissions?.collections?.[collection.slug]?.read?.permission) {
|
|
||||||
return (
|
|
||||||
<DocumentInfoProvider
|
|
||||||
key={`${collection.slug}-edit-${id}-${locale}`}
|
|
||||||
collection={collection}
|
|
||||||
id={id}
|
|
||||||
>
|
|
||||||
<Edit
|
|
||||||
isEditing
|
|
||||||
{...routeProps}
|
|
||||||
collection={collection}
|
|
||||||
/>
|
|
||||||
</DocumentInfoProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Unauthorized />;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (collection.versions) {
|
|
||||||
routesToReturn.push(
|
|
||||||
<Route
|
<Route
|
||||||
key={`${collection.slug}-versions`}
|
key={`${collection.slug}-list`}
|
||||||
path={`${match.url}/collections/${collection.slug}/:id/versions`}
|
path={`${match.url}/collections/${collection.slug}`}
|
||||||
exact
|
exact
|
||||||
render={(routeProps) => {
|
render={(routeProps) => {
|
||||||
if (permissions?.collections?.[collection.slug]?.readVersions?.permission) {
|
if (permissions?.collections?.[collection.slug]?.read?.permission) {
|
||||||
return (
|
return (
|
||||||
<Versions
|
<List
|
||||||
{...routeProps}
|
{...routeProps}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
/>
|
/>
|
||||||
@@ -263,21 +198,15 @@ const Routes = () => {
|
|||||||
return <Unauthorized />;
|
return <Unauthorized />;
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
);
|
|
||||||
|
|
||||||
routesToReturn.push(
|
|
||||||
<Route
|
<Route
|
||||||
key={`${collection.slug}-view-version`}
|
key={`${collection.slug}-create`}
|
||||||
path={`${match.url}/collections/${collection.slug}/:id/versions/:versionID`}
|
path={`${match.url}/collections/${collection.slug}/create`}
|
||||||
exact
|
exact
|
||||||
render={(routeProps) => {
|
render={(routeProps) => {
|
||||||
if (permissions?.collections?.[collection.slug]?.readVersions?.permission) {
|
if (permissions?.collections?.[collection.slug]?.create?.permission) {
|
||||||
return (
|
return (
|
||||||
<DocumentInfoProvider
|
<DocumentInfoProvider collection={collection}>
|
||||||
collection={collection}
|
<Edit
|
||||||
id={routeProps.match.params.id}
|
|
||||||
>
|
|
||||||
<Version
|
|
||||||
{...routeProps}
|
{...routeProps}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
/>
|
/>
|
||||||
@@ -288,81 +217,154 @@ const Routes = () => {
|
|||||||
return <Unauthorized />;
|
return <Unauthorized />;
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return routesToReturn;
|
|
||||||
}, [])}
|
|
||||||
|
|
||||||
{globals && globals.reduce((globalRoutes, global) => {
|
|
||||||
const routesToReturn = [
|
|
||||||
...globalRoutes,
|
|
||||||
<Route
|
|
||||||
key={`${global.slug}`}
|
|
||||||
path={`${match.url}/globals/${global.slug}`}
|
|
||||||
exact
|
|
||||||
render={(routeProps) => {
|
|
||||||
if (permissions?.globals?.[global.slug]?.read?.permission) {
|
|
||||||
return (
|
|
||||||
<DocumentInfoProvider
|
|
||||||
global={global}
|
|
||||||
key={`${global.slug}-${locale}`}
|
|
||||||
>
|
|
||||||
<EditGlobal
|
|
||||||
{...routeProps}
|
|
||||||
global={global}
|
|
||||||
/>
|
|
||||||
</DocumentInfoProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Unauthorized />;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (global.versions) {
|
|
||||||
routesToReturn.push(
|
|
||||||
<Route
|
<Route
|
||||||
key={`${global.slug}-versions`}
|
key={`${collection.slug}-edit`}
|
||||||
path={`${match.url}/globals/${global.slug}/versions`}
|
path={`${match.url}/collections/${collection.slug}/:id`}
|
||||||
exact
|
exact
|
||||||
render={(routeProps) => {
|
render={(routeProps) => {
|
||||||
if (permissions?.globals?.[global.slug]?.readVersions?.permission) {
|
const { match: { params: { id } } } = routeProps;
|
||||||
|
if (permissions?.collections?.[collection.slug]?.read?.permission) {
|
||||||
return (
|
return (
|
||||||
<Versions
|
<DocumentInfoProvider
|
||||||
{...routeProps}
|
key={`${collection.slug}-edit-${id}-${locale}`}
|
||||||
global={global}
|
collection={collection}
|
||||||
/>
|
id={id}
|
||||||
|
>
|
||||||
|
<Edit
|
||||||
|
isEditing
|
||||||
|
{...routeProps}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</DocumentInfoProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Unauthorized />;
|
return <Unauthorized />;
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
);
|
];
|
||||||
routesToReturn.push(
|
|
||||||
|
if (collection.versions) {
|
||||||
|
routesToReturn.push(
|
||||||
|
<Route
|
||||||
|
key={`${collection.slug}-versions`}
|
||||||
|
path={`${match.url}/collections/${collection.slug}/:id/versions`}
|
||||||
|
exact
|
||||||
|
render={(routeProps) => {
|
||||||
|
if (permissions?.collections?.[collection.slug]?.readVersions?.permission) {
|
||||||
|
return (
|
||||||
|
<Versions
|
||||||
|
{...routeProps}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Unauthorized />;
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
routesToReturn.push(
|
||||||
|
<Route
|
||||||
|
key={`${collection.slug}-view-version`}
|
||||||
|
path={`${match.url}/collections/${collection.slug}/:id/versions/:versionID`}
|
||||||
|
exact
|
||||||
|
render={(routeProps) => {
|
||||||
|
if (permissions?.collections?.[collection.slug]?.readVersions?.permission) {
|
||||||
|
return (
|
||||||
|
<DocumentInfoProvider
|
||||||
|
collection={collection}
|
||||||
|
id={routeProps.match.params.id}
|
||||||
|
>
|
||||||
|
<Version
|
||||||
|
{...routeProps}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</DocumentInfoProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Unauthorized />;
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return routesToReturn;
|
||||||
|
}, [])}
|
||||||
|
|
||||||
|
{globals && globals
|
||||||
|
.filter(({ admin: { hidden } }) => !(typeof hidden === 'function' ? hidden({ user }) : hidden))
|
||||||
|
.reduce((globalRoutes, global) => {
|
||||||
|
const routesToReturn = [
|
||||||
|
...globalRoutes,
|
||||||
<Route
|
<Route
|
||||||
key={`${global.slug}-view-version`}
|
key={`${global.slug}`}
|
||||||
path={`${match.url}/globals/${global.slug}/versions/:versionID`}
|
path={`${match.url}/globals/${global.slug}`}
|
||||||
exact
|
exact
|
||||||
render={(routeProps) => {
|
render={(routeProps) => {
|
||||||
if (permissions?.globals?.[global.slug]?.readVersions?.permission) {
|
if (permissions?.globals?.[global.slug]?.read?.permission) {
|
||||||
return (
|
return (
|
||||||
<Version
|
<DocumentInfoProvider
|
||||||
{...routeProps}
|
|
||||||
global={global}
|
global={global}
|
||||||
/>
|
key={`${global.slug}-${locale}`}
|
||||||
|
>
|
||||||
|
<EditGlobal
|
||||||
|
{...routeProps}
|
||||||
|
global={global}
|
||||||
|
/>
|
||||||
|
</DocumentInfoProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Unauthorized />;
|
return <Unauthorized />;
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
);
|
];
|
||||||
}
|
|
||||||
return routesToReturn;
|
if (global.versions) {
|
||||||
}, [])}
|
routesToReturn.push(
|
||||||
|
<Route
|
||||||
|
key={`${global.slug}-versions`}
|
||||||
|
path={`${match.url}/globals/${global.slug}/versions`}
|
||||||
|
exact
|
||||||
|
render={(routeProps) => {
|
||||||
|
if (permissions?.globals?.[global.slug]?.readVersions?.permission) {
|
||||||
|
return (
|
||||||
|
<Versions
|
||||||
|
{...routeProps}
|
||||||
|
global={global}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Unauthorized />;
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
routesToReturn.push(
|
||||||
|
<Route
|
||||||
|
key={`${global.slug}-view-version`}
|
||||||
|
path={`${match.url}/globals/${global.slug}/versions/:versionID`}
|
||||||
|
exact
|
||||||
|
render={(routeProps) => {
|
||||||
|
if (permissions?.globals?.[global.slug]?.readVersions?.permission) {
|
||||||
|
return (
|
||||||
|
<Version
|
||||||
|
{...routeProps}
|
||||||
|
global={global}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Unauthorized />;
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return routesToReturn;
|
||||||
|
}, [])}
|
||||||
|
|
||||||
<Route path={`${match.url}*`}>
|
<Route path={`${match.url}*`}>
|
||||||
<NotFound />
|
<NotFound />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { NavLink, Link, useHistory } from 'react-router-dom';
|
import { Link, NavLink, useHistory } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useConfig } from '../../utilities/Config';
|
import { useConfig } from '../../utilities/Config';
|
||||||
import { useAuth } from '../../utilities/Auth';
|
import { useAuth } from '../../utilities/Auth';
|
||||||
@@ -12,7 +12,7 @@ import Account from '../../graphics/Account';
|
|||||||
import Localizer from '../Localizer';
|
import Localizer from '../Localizer';
|
||||||
import NavGroup from '../NavGroup';
|
import NavGroup from '../NavGroup';
|
||||||
import Logout from '../Logout';
|
import Logout from '../Logout';
|
||||||
import { groupNavItems, Group, EntityToGroup, EntityType } from '../../../utilities/groupNavItems';
|
import { EntityToGroup, EntityType, Group, groupNavItems } from '../../../utilities/groupNavItems';
|
||||||
import { getTranslation } from '../../../../utilities/getTranslation';
|
import { getTranslation } from '../../../../utilities/getTranslation';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
@@ -20,7 +20,7 @@ import './index.scss';
|
|||||||
const baseClass = 'nav';
|
const baseClass = 'nav';
|
||||||
|
|
||||||
const DefaultNav = () => {
|
const DefaultNav = () => {
|
||||||
const { permissions } = useAuth();
|
const { permissions, user } = useAuth();
|
||||||
const [menuActive, setMenuActive] = useState(false);
|
const [menuActive, setMenuActive] = useState(false);
|
||||||
const [groups, setGroups] = useState<Group[]>([]);
|
const [groups, setGroups] = useState<Group[]>([]);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -47,24 +47,28 @@ const DefaultNav = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setGroups(groupNavItems([
|
setGroups(groupNavItems([
|
||||||
...collections.map((collection) => {
|
...collections
|
||||||
const entityToGroup: EntityToGroup = {
|
.filter(({ admin: { hidden } }) => !(typeof hidden === 'function' ? hidden({ user }) : hidden))
|
||||||
type: EntityType.collection,
|
.map((collection) => {
|
||||||
entity: collection,
|
const entityToGroup: EntityToGroup = {
|
||||||
};
|
type: EntityType.collection,
|
||||||
|
entity: collection,
|
||||||
|
};
|
||||||
|
|
||||||
return entityToGroup;
|
return entityToGroup;
|
||||||
}),
|
}),
|
||||||
...globals.map((global) => {
|
...globals
|
||||||
const entityToGroup: EntityToGroup = {
|
.filter(({ admin: { hidden } }) => !(typeof hidden === 'function' ? hidden({ user }) : hidden))
|
||||||
type: EntityType.global,
|
.map((global) => {
|
||||||
entity: global,
|
const entityToGroup: EntityToGroup = {
|
||||||
};
|
type: EntityType.global,
|
||||||
|
entity: global,
|
||||||
|
};
|
||||||
|
|
||||||
return entityToGroup;
|
return entityToGroup;
|
||||||
}),
|
}),
|
||||||
], permissions, i18n));
|
], permissions, i18n));
|
||||||
}, [collections, globals, permissions, i18n, i18n.language]);
|
}, [collections, globals, permissions, i18n, i18n.language, user]);
|
||||||
|
|
||||||
useEffect(() => history.listen(() => {
|
useEffect(() => history.listen(() => {
|
||||||
setMenuActive(false);
|
setMenuActive(false);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import Card from '../../elements/Card';
|
|||||||
import Button from '../../elements/Button';
|
import Button from '../../elements/Button';
|
||||||
import { Props } from './types';
|
import { Props } from './types';
|
||||||
import { Gutter } from '../../elements/Gutter';
|
import { Gutter } from '../../elements/Gutter';
|
||||||
import { groupNavItems, Group, EntityToGroup, EntityType } from '../../../utilities/groupNavItems';
|
import { EntityToGroup, EntityType, Group, groupNavItems } from '../../../utilities/groupNavItems';
|
||||||
import { getTranslation } from '../../../../utilities/getTranslation';
|
import { getTranslation } from '../../../../utilities/getTranslation';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
@@ -20,6 +20,7 @@ const Dashboard: React.FC<Props> = (props) => {
|
|||||||
collections,
|
collections,
|
||||||
globals,
|
globals,
|
||||||
permissions,
|
permissions,
|
||||||
|
user,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { push } = useHistory();
|
const { push } = useHistory();
|
||||||
@@ -41,24 +42,28 @@ const Dashboard: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setGroups(groupNavItems([
|
setGroups(groupNavItems([
|
||||||
...collections.map((collection) => {
|
...collections
|
||||||
const entityToGroup: EntityToGroup = {
|
.filter(({ admin: { hidden } }) => !(typeof hidden === 'function' ? hidden({ user }) : hidden))
|
||||||
type: EntityType.collection,
|
.map((collection) => {
|
||||||
entity: collection,
|
const entityToGroup: EntityToGroup = {
|
||||||
};
|
type: EntityType.collection,
|
||||||
|
entity: collection,
|
||||||
|
};
|
||||||
|
|
||||||
return entityToGroup;
|
return entityToGroup;
|
||||||
}),
|
}),
|
||||||
...globals.map((global) => {
|
...globals
|
||||||
const entityToGroup: EntityToGroup = {
|
.filter(({ admin: { hidden } }) => !(typeof hidden === 'function' ? hidden({ user }) : hidden))
|
||||||
type: EntityType.global,
|
.map((global) => {
|
||||||
entity: global,
|
const entityToGroup: EntityToGroup = {
|
||||||
};
|
type: EntityType.global,
|
||||||
|
entity: global,
|
||||||
|
};
|
||||||
|
|
||||||
return entityToGroup;
|
return entityToGroup;
|
||||||
}),
|
}),
|
||||||
], permissions, i18n));
|
], permissions, i18n));
|
||||||
}, [collections, globals, i18n, permissions]);
|
}, [collections, globals, i18n, permissions, user]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={baseClass}>
|
<div className={baseClass}>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useConfig } from '../../utilities/Config';
|
import { useConfig } from '../../utilities/Config';
|
||||||
import { useAuth } from '../../utilities/Auth';
|
import { useAuth } from '../../utilities/Auth';
|
||||||
import { useStepNav } from '../../elements/StepNav';
|
import { useStepNav } from '../../elements/StepNav';
|
||||||
@@ -6,7 +6,7 @@ import RenderCustomComponent from '../../utilities/RenderCustomComponent';
|
|||||||
import DefaultDashboard from './Default';
|
import DefaultDashboard from './Default';
|
||||||
|
|
||||||
const Dashboard: React.FC = () => {
|
const Dashboard: React.FC = () => {
|
||||||
const { permissions } = useAuth();
|
const { permissions, user } = useAuth();
|
||||||
const { setStepNav } = useStepNav();
|
const { setStepNav } = useStepNav();
|
||||||
const [filteredGlobals, setFilteredGlobals] = useState([]);
|
const [filteredGlobals, setFilteredGlobals] = useState([]);
|
||||||
|
|
||||||
@@ -42,6 +42,7 @@ const Dashboard: React.FC = () => {
|
|||||||
globals: filteredGlobals,
|
globals: filteredGlobals,
|
||||||
collections: collections.filter((collection) => permissions?.collections?.[collection.slug]?.read?.permission),
|
collections: collections.filter((collection) => permissions?.collections?.[collection.slug]?.read?.permission),
|
||||||
permissions,
|
permissions,
|
||||||
|
user,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
|
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
|
||||||
import { SanitizedGlobalConfig } from '../../../../globals/config/types';
|
import { SanitizedGlobalConfig } from '../../../../globals/config/types';
|
||||||
import { Permissions } from '../../../../auth/types';
|
import { Permissions, User } from '../../../../auth/types';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
collections: SanitizedCollectionConfig[],
|
collections: SanitizedCollectionConfig[],
|
||||||
globals: SanitizedGlobalConfig[],
|
globals: SanitizedGlobalConfig[],
|
||||||
permissions: Permissions
|
permissions: Permissions,
|
||||||
|
user: User,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ const collectionSchema = joi.object().keys({
|
|||||||
}),
|
}),
|
||||||
timestamps: joi.boolean(),
|
timestamps: joi.boolean(),
|
||||||
admin: joi.object({
|
admin: joi.object({
|
||||||
|
hidden: joi.alternatives().try(
|
||||||
|
joi.boolean(),
|
||||||
|
joi.func(),
|
||||||
|
),
|
||||||
useAsTitle: joi.string(),
|
useAsTitle: joi.string(),
|
||||||
defaultColumns: joi.array().items(joi.string()),
|
defaultColumns: joi.array().items(joi.string()),
|
||||||
listSearchableFields: joi.array().items(joi.string()),
|
listSearchableFields: joi.array().items(joi.string()),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Response } from 'express';
|
|||||||
import { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types';
|
import { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types';
|
||||||
import { Field } from '../../fields/config/types';
|
import { Field } from '../../fields/config/types';
|
||||||
import { PayloadRequest } from '../../express/types';
|
import { PayloadRequest } from '../../express/types';
|
||||||
import { Auth, IncomingAuthType } from '../../auth/types';
|
import { Auth, IncomingAuthType, User } from '../../auth/types';
|
||||||
import { IncomingUploadType, Upload } from '../../uploads/types';
|
import { IncomingUploadType, Upload } from '../../uploads/types';
|
||||||
import { IncomingCollectionVersions, SanitizedCollectionVersions } from '../../versions/types';
|
import { IncomingCollectionVersions, SanitizedCollectionVersions } from '../../versions/types';
|
||||||
import { Config as GeneratedTypes } from '../../generated-types';
|
import { Config as GeneratedTypes } from '../../generated-types';
|
||||||
@@ -153,6 +153,10 @@ type BeforeDuplicateArgs<T> = {
|
|||||||
export type BeforeDuplicate<T = any> = (args: BeforeDuplicateArgs<T>) => T | Promise<T>
|
export type BeforeDuplicate<T = any> = (args: BeforeDuplicateArgs<T>) => T | Promise<T>
|
||||||
|
|
||||||
export type CollectionAdminOptions = {
|
export type CollectionAdminOptions = {
|
||||||
|
/**
|
||||||
|
* Exclude the collection from the admin nav and routes
|
||||||
|
*/
|
||||||
|
hidden?: ((args: { user: User }) => boolean) | boolean;
|
||||||
/**
|
/**
|
||||||
* Field to use as title in Edit view and first column in List view
|
* Field to use as title in Edit view and first column in List view
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ const globalSchema = joi.object().keys({
|
|||||||
joi.object().pattern(joi.string(), [joi.string()]),
|
joi.object().pattern(joi.string(), [joi.string()]),
|
||||||
),
|
),
|
||||||
admin: joi.object({
|
admin: joi.object({
|
||||||
|
hidden: joi.alternatives().try(
|
||||||
|
joi.boolean(),
|
||||||
|
joi.func(),
|
||||||
|
),
|
||||||
group: joi.alternatives().try(
|
group: joi.alternatives().try(
|
||||||
joi.string(),
|
joi.string(),
|
||||||
joi.object().pattern(joi.string(), [joi.string()]),
|
joi.object().pattern(joi.string(), [joi.string()]),
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Model, Document } from 'mongoose';
|
import { Document, Model } from 'mongoose';
|
||||||
import { DeepRequired } from 'ts-essentials';
|
import { DeepRequired } from 'ts-essentials';
|
||||||
import { GraphQLNonNull, GraphQLObjectType } from 'graphql';
|
import { GraphQLNonNull, GraphQLObjectType } from 'graphql';
|
||||||
|
import { User } from '../../auth/types';
|
||||||
import { PayloadRequest } from '../../express/types';
|
import { PayloadRequest } from '../../express/types';
|
||||||
import { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types';
|
import { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types';
|
||||||
import { Field } from '../../fields/config/types';
|
import { Field } from '../../fields/config/types';
|
||||||
@@ -46,6 +47,10 @@ export interface GlobalModel extends Model<Document> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type GlobalAdminOptions = {
|
export type GlobalAdminOptions = {
|
||||||
|
/**
|
||||||
|
* Exclude the global from the admin nav and routes
|
||||||
|
*/
|
||||||
|
hidden?: ((args: { user: User }) => boolean) | boolean;
|
||||||
/**
|
/**
|
||||||
* Place globals into a navigational group
|
* Place globals into a navigational group
|
||||||
* */
|
* */
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import CustomMinimalRoute from './components/views/CustomMinimal';
|
|||||||
import CustomDefaultRoute from './components/views/CustomDefault';
|
import CustomDefaultRoute from './components/views/CustomDefault';
|
||||||
import BeforeLogin from './components/BeforeLogin';
|
import BeforeLogin from './components/BeforeLogin';
|
||||||
import AfterNavLinks from './components/AfterNavLinks';
|
import AfterNavLinks from './components/AfterNavLinks';
|
||||||
import { slug, globalSlug } from './shared';
|
import { globalSlug, slug } from './shared';
|
||||||
import Logout from './components/Logout';
|
import Logout from './components/Logout';
|
||||||
import DemoUIFieldField from './components/DemoUIField/Field';
|
import DemoUIFieldField from './components/DemoUIField/Field';
|
||||||
import DemoUIFieldCell from './components/DemoUIField/Cell';
|
import DemoUIFieldCell from './components/DemoUIField/Cell';
|
||||||
@@ -68,6 +68,18 @@ export default buildConfig({
|
|||||||
auth: true,
|
auth: true,
|
||||||
fields: [],
|
fields: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
slug: 'hidden-collection',
|
||||||
|
admin: {
|
||||||
|
hidden: () => true,
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
slug,
|
slug,
|
||||||
labels: {
|
labels: {
|
||||||
@@ -176,6 +188,18 @@ export default buildConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
globals: [
|
globals: [
|
||||||
|
{
|
||||||
|
slug: 'hidden-global',
|
||||||
|
admin: {
|
||||||
|
hidden: () => true,
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
slug: globalSlug,
|
slug: globalSlug,
|
||||||
label: {
|
label: {
|
||||||
|
|||||||
@@ -111,6 +111,24 @@ describe('admin', () => {
|
|||||||
await page.locator(`.step-nav >> text=${slug}`).click();
|
await page.locator(`.step-nav >> text=${slug}`).click();
|
||||||
expect(page.url()).toContain(url.list);
|
expect(page.url()).toContain(url.list);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not show hidden collections and globals', async () => {
|
||||||
|
await page.goto(url.admin);
|
||||||
|
|
||||||
|
// nav menu
|
||||||
|
await expect(await page.locator('#nav-hidden-collection')).toBeHidden();
|
||||||
|
await expect(await page.locator('#nav-hidden-global')).toBeHidden();
|
||||||
|
|
||||||
|
// dashboard
|
||||||
|
await expect(await page.locator('#card-hidden-collection')).toBeHidden();
|
||||||
|
await expect(await page.locator('#card-hidden-global')).toBeHidden();
|
||||||
|
|
||||||
|
// routing
|
||||||
|
await page.goto(url.collection('hidden-collection'));
|
||||||
|
await expect(await page.locator('.not-found')).toContainText('Nothing found');
|
||||||
|
await page.goto(url.global('hidden-global'));
|
||||||
|
await expect(await page.locator('.not-found')).toContainText('Nothing found');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('CRUD', () => {
|
describe('CRUD', () => {
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ export class AdminUrlUtil {
|
|||||||
return `${this.list}/${id}`;
|
return `${this.list}/${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
collection(slug: string): string {
|
||||||
|
return `${this.admin}/collections/${slug}`;
|
||||||
|
}
|
||||||
|
|
||||||
global(slug: string): string {
|
global(slug: string): string {
|
||||||
return `${this.admin}/globals/${slug}`;
|
return `${this.admin}/globals/${slug}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user