feat: admin UI logout extensibility (#1274)
* added Logout documentation * updated type and schema * updated logout component, route and inactivityRoute references * added custom Logout component into test admin instance * fixed windows path management * added dotenv usage * added check on testSuiteDir and provided more meaningful error message * fixed object destructure * updated from logout.route to logoutRoute * extracted getSanitizedLogoutRoutes method * added unit tests * updated references * updated doc * reviewed casing and added defaults * updated usage * restored workers previous value * fixed config validation * updated docs and schema * updated reference to logoutRoute and inactivityRoute * updated test ref Co-authored-by: Alberto Maghini (MSC Technology Italia) <alberto.maghini@msc.com> Co-authored-by: Alberto Maghini <alberto@newesis.com>
This commit is contained in:
@@ -11,8 +11,10 @@ While designing the Payload Admin panel, we determined it should be as minimal a
|
||||
To swap in your own React component, first, consult the list of available component overrides below. Determine the scope that corresponds to what you are trying to accomplish, and then author your React component accordingly.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong><br/>
|
||||
Custom components will automatically be provided with all props that the default component would accept.
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
Custom components will automatically be provided with all props that the
|
||||
default component would accept.
|
||||
</Banner>
|
||||
|
||||
### Base Component Overrides
|
||||
@@ -20,10 +22,11 @@ To swap in your own React component, first, consult the list of available compon
|
||||
You can override a set of admin panel-wide components by providing a component to your base Payload config's `admin.components` property. The following options are available:
|
||||
|
||||
| Path | Description |
|
||||
| --------------------- | -------------|
|
||||
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`Nav`** | Contains the sidebar and mobile Nav in its entirety. |
|
||||
| **`logout.Button`** | A custom React component.
|
||||
| **`BeforeDashboard`** | Array of components to inject into the built-in Dashboard, _before_ the default dashboard contents. |
|
||||
| **`AfterDashboard`** | Array of components to inject into the built-in Dashboard, _after_ the default dashboard contents. [Demo](https://github.com/payloadcms/payload/tree/master/test/admin/components/AfterDashboard/index.tsx)|
|
||||
| **`AfterDashboard`** | Array of components to inject into the built-in Dashboard, _after_ the default dashboard contents. [Demo](https://github.com/payloadcms/payload/tree/master/test/admin/components/AfterDashboard/index.tsx) |
|
||||
| **`BeforeLogin`** | Array of components to inject into the built-in Login, _before_ the default login form. |
|
||||
| **`AfterLogin`** | Array of components to inject into the built-in Login, _after_ the default login form. |
|
||||
| **`BeforeNavLinks`** | Array of components to inject into the built-in Nav, _before_ the links themselves. |
|
||||
@@ -38,8 +41,9 @@ You can override a set of admin panel-wide components by providing a component t
|
||||
#### Full example:
|
||||
|
||||
`payload.config.js`
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { buildConfig } from "payload/config";
|
||||
import {
|
||||
MyCustomNav,
|
||||
MyCustomLogo,
|
||||
@@ -47,7 +51,7 @@ import {
|
||||
MyCustomAccount,
|
||||
MyCustomDashboard,
|
||||
MyProvider,
|
||||
} from './customComponents';
|
||||
} from "./customComponents";
|
||||
|
||||
export default buildConfig({
|
||||
admin: {
|
||||
@@ -67,14 +71,14 @@ export default buildConfig({
|
||||
});
|
||||
```
|
||||
|
||||
*For more examples regarding how to customize components, look at the following [examples](https://github.com/payloadcms/payload/tree/master/test/admin/components).*
|
||||
_For more examples regarding how to customize components, look at the following [examples](https://github.com/payloadcms/payload/tree/master/test/admin/components)._
|
||||
|
||||
### Collections
|
||||
|
||||
You can override components on a Collection-by-Collection basis via each Collection's `admin` property.
|
||||
|
||||
| Path | Description |
|
||||
| ---------------- | -------------|
|
||||
| ---------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| **`views.Edit`** | Used while a document within this Collection is being edited. |
|
||||
| **`views.List`** | The `List` view is used to render a paginated, filterable table of Documents in this Collection. |
|
||||
|
||||
@@ -83,7 +87,7 @@ You can override components on a Collection-by-Collection basis via each Collect
|
||||
As with Collections, You can override components on a global-by-global basis via their `admin` property.
|
||||
|
||||
| Path | Description |
|
||||
| ---------------- | -------------|
|
||||
| ---------------- | --------------------------------------- |
|
||||
| **`views.Edit`** | Used while this Global is being edited. |
|
||||
|
||||
### Fields
|
||||
@@ -91,14 +95,18 @@ As with Collections, You can override components on a global-by-global basis via
|
||||
All Payload fields support the ability to swap in your own React components. So, for example, instead of rendering a default Text input, you might need to render a color picker that provides the editor with a custom color picker interface to restrict the data entered to colors only.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong><br/>
|
||||
Don't see a built-in field type that you need? Build it! Using a combination of custom validation and custom components, you can override the entirety of how a component functions within the admin panel and effectively create your own field type.
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
Don't see a built-in field type that you need? Build it! Using a combination
|
||||
of custom validation and custom components, you can override the entirety of
|
||||
how a component functions within the admin panel and effectively create your
|
||||
own field type.
|
||||
</Banner>
|
||||
|
||||
**Fields support the following custom components:**
|
||||
|
||||
| Component | Description |
|
||||
| --------------- |------------------------------------------------------------------------------------------------------------------------------|
|
||||
| ------------ | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`Filter`** | Override the text input that is presented in the `List` view when a user is filtering documents by the customized field. |
|
||||
| **`Cell`** | Used in the `List` view's table to represent a table-based preview of the data stored in the field. [More](#cell-component) |
|
||||
| **`Field`** | Swap out the field itself within all `Edit` views. [More](#field-component) |
|
||||
@@ -108,7 +116,7 @@ All Payload fields support the ability to swap in your own React components. So,
|
||||
These are the props that will be passed to your custom Cell to use in your own components.
|
||||
|
||||
| Property | Description |
|
||||
|--------------|-------------------------------------------------------------------|
|
||||
| ---------------- | ----------------------------------------------------------------- |
|
||||
| **`field`** | An object that includes the field configuration. |
|
||||
| **`colIndex`** | A unique number for the column in the list. |
|
||||
| **`collection`** | An object with the config of the collection that the field is in. |
|
||||
@@ -118,24 +126,14 @@ These are the props that will be passed to your custom Cell to use in your own c
|
||||
#### Example
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import './index.scss';
|
||||
const baseClass = 'custom-cell';
|
||||
import React from "react";
|
||||
import "./index.scss";
|
||||
const baseClass = "custom-cell";
|
||||
|
||||
const CustomCell: React.FC<Props> = (props) => {
|
||||
const {
|
||||
field,
|
||||
colIndex,
|
||||
collection,
|
||||
cellData,
|
||||
rowData,
|
||||
} = props;
|
||||
const { field, colIndex, collection, cellData, rowData } = props;
|
||||
|
||||
return (
|
||||
<span className={baseClass}>
|
||||
{ cellData }
|
||||
</span>
|
||||
);
|
||||
return <span className={baseClass}>{cellData}</span>;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -148,21 +146,28 @@ When writing your own custom components you can make use of a number of hooks to
|
||||
When swapping out the `Field` component, you'll be responsible for sending and receiving the field's `value` from the form itself. To do so, import the `useField` hook as follows:
|
||||
|
||||
```tsx
|
||||
import { useField } from 'payload/components/forms'
|
||||
import { useField } from "payload/components/forms";
|
||||
|
||||
type Props = { path: string }
|
||||
type Props = { path: string };
|
||||
|
||||
const CustomTextField: React.FC<Props> = ({ path }) => {
|
||||
// highlight-start
|
||||
const { value, setValue } = useField<Props>({ path })
|
||||
const { value, setValue } = useField<Props>({ path });
|
||||
// highlight-end
|
||||
|
||||
return <input onChange={e => setValue(e.target.value)} value={value.path} />
|
||||
}
|
||||
return (
|
||||
<input onChange={(e) => setValue(e.target.value)} value={value.path} />
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
For more information regarding the hooks that are available to you while you build custom components, including the <strong>useField</strong> hook, <a href="/docs/admin/hooks" style={{color: 'black'}}>click here</a>.
|
||||
For more information regarding the hooks that are available to you while you
|
||||
build custom components, including the <strong>useField</strong> hook,{" "}
|
||||
<a href="/docs/admin/hooks" style={{ color: "black" }}>
|
||||
click here
|
||||
</a>
|
||||
.
|
||||
</Banner>
|
||||
|
||||
## Custom routes
|
||||
@@ -172,27 +177,30 @@ You can easily add your own custom routes to the Payload Admin panel using the `
|
||||
**Custom routes support the following properties:**
|
||||
|
||||
| Property | Description |
|
||||
| ----------------- | -------------|
|
||||
| **`Component`** * | Pass in the component that should be rendered when a user navigates to this route. |
|
||||
| **`path`** * | React Router `path`. [See the React Router docs](https://v5.reactrouter.com/web/api/Route/path-string-string) for more info. |
|
||||
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`Component`** \* | Pass in the component that should be rendered when a user navigates to this route. |
|
||||
| **`path`** \* | React Router `path`. [See the React Router docs](https://v5.reactrouter.com/web/api/Route/path-string-string) for more info. |
|
||||
| **`exact`** | React Router `exact` property. [More](https://v5.reactrouter.com/web/api/Route/exact-bool) |
|
||||
| **`strict`** | React Router `strict` property. [More](https://v5.reactrouter.com/web/api/Route/strict-bool) |
|
||||
| **`sensitive`** | React Router `sensitive` property. [More](https://v5.reactrouter.com/web/api/Route/sensitive-bool) |
|
||||
|
||||
*\* An asterisk denotes that a property is required.*
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
#### Custom route components
|
||||
|
||||
Your custom route components will be given all the props that a React Router `<Route />` typically would receive, as well as two props from Payload:
|
||||
|
||||
| Prop | Description |
|
||||
| ---------------------- | -------------|
|
||||
| ----------------------- | ---------------------------------------------------------------------------- |
|
||||
| **`user`** | The currently logged in user. Will be `null` if no user is logged in. |
|
||||
| **`canAccessAdmin`** * | If the currently logged in user is allowed to access the admin panel or not. |
|
||||
| **`canAccessAdmin`** \* | If the currently logged in user is allowed to access the admin panel or not. |
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong><br/>
|
||||
It's up to you to secure your custom routes. If your route requires a user to be logged in or to have certain access rights, you should handle that within your route component yourself.
|
||||
<strong>Note:</strong>
|
||||
<br />
|
||||
It's up to you to secure your custom routes. If your route requires a user to
|
||||
be logged in or to have certain access rights, you should handle that within
|
||||
your route component yourself.
|
||||
</Banner>
|
||||
|
||||
#### Example
|
||||
@@ -208,7 +216,10 @@ To see how to pass in your custom views to create custom routes of your own, tak
|
||||
|
||||
As your admin customizations gets more complex you may want to share state between fields or other components. You can add custom providers to do add your own context to any Payload app for use in other custom components within the admin panel. Within your config add `admin.components.providers`, these can be used to share context or provide other custom functionality. Read the [React context](https://reactjs.org/docs/context.html) docs to learn more.
|
||||
|
||||
<Banner type="warning"><strong>Reminder:</strong> Don't forget to pass the **children** prop through the provider component for the admin UI to show</Banner>
|
||||
<Banner type="warning">
|
||||
<strong>Reminder:</strong> Don't forget to pass the **children** prop through
|
||||
the provider component for the admin UI to show
|
||||
</Banner>
|
||||
|
||||
### Styling Custom Components
|
||||
|
||||
@@ -225,7 +236,7 @@ To make use of Payload SCSS variables / mixins to use directly in your own compo
|
||||
In any custom component you can get the selected locale with the `useLocale` hook. Here is a simple example:
|
||||
|
||||
```tsx
|
||||
import { useLocale } from 'payload/components/utilities';
|
||||
import { useLocale } from "payload/components/utilities";
|
||||
|
||||
const Greeting: React.FC = () => {
|
||||
// highlight-start
|
||||
@@ -233,12 +244,10 @@ const Greeting: React.FC = () => {
|
||||
// highlight-end
|
||||
|
||||
const trans = {
|
||||
en: 'Hello',
|
||||
es: 'Hola',
|
||||
en: "Hello",
|
||||
es: "Hola",
|
||||
};
|
||||
|
||||
return (
|
||||
<span> { trans[locale] } </span>
|
||||
);
|
||||
return <span> {trans[locale]} </span>;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -23,7 +23,7 @@ The Payload Admin panel is built with Webpack, code-split, highly performant (ev
|
||||
All options for the Admin panel are defined in your base Payload config file.
|
||||
|
||||
| Option | Description |
|
||||
| -------------------- | -------------|
|
||||
| --------------------- | -------------|
|
||||
| `user` | The `slug` of a Collection that you want be used to log in to the Admin dashboard. [More](/docs/admin/overview#the-admin-user-collection) |
|
||||
| `meta` | Base meta data to use for the Admin panel. Included properties are `titleSuffix`, `ogImage`, and `favicon`. |
|
||||
| `disable` | If set to `true`, the entire Admin panel will be disabled. |
|
||||
@@ -33,7 +33,9 @@ All options for the Admin panel are defined in your base Payload config file.
|
||||
| `dateFormat` | Global date format that will be used for all dates in the Admin panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
|
||||
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
|
||||
| `components` | Component overrides that affect the entirety of the Admin panel. [More](/docs/admin/components) |
|
||||
| `webpack` | Customize the Webpack config that's used to generate the Admin panel. [More](/docs/admin/webpack) |
|
||||
| `webpack` | Customize the Webpack config that's used to generate the Admin panel. [More](/docs/admin/webpack) | |
|
||||
| **`logoutRoute`** | The route for the `logout` page. |
|
||||
| **`inactivityRoute`** | The route for the `logout` inactivity page. |
|
||||
|
||||
|
||||
### The Admin User Collection
|
||||
|
||||
@@ -32,9 +32,12 @@ const Routes = () => {
|
||||
|
||||
const canAccessAdmin = permissions?.canAccessAdmin;
|
||||
|
||||
const config = useConfig();
|
||||
const {
|
||||
admin: {
|
||||
user: userSlug,
|
||||
logoutRoute,
|
||||
inactivityRoute: logoutInactivityRoute,
|
||||
components: {
|
||||
routes: customRoutes,
|
||||
} = {},
|
||||
@@ -42,7 +45,8 @@ const Routes = () => {
|
||||
routes,
|
||||
collections,
|
||||
globals,
|
||||
} = useConfig();
|
||||
} = config;
|
||||
|
||||
|
||||
const userCollection = collections.find(({ slug }) => slug === userSlug);
|
||||
|
||||
@@ -103,10 +107,10 @@ const Routes = () => {
|
||||
<Route path={`${match.url}/login`}>
|
||||
<Login />
|
||||
</Route>
|
||||
<Route path={`${match.url}/logout`}>
|
||||
<Route path={`${match.url}${logoutRoute}`}>
|
||||
<Logout />
|
||||
</Route>
|
||||
<Route path={`${match.url}/logout-inactivity`}>
|
||||
<Route path={`${match.url}${logoutInactivityRoute}`}>
|
||||
<Logout inactivity />
|
||||
</Route>
|
||||
|
||||
|
||||
44
src/admin/components/elements/Logout/index.tsx
Normal file
44
src/admin/components/elements/Logout/index.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useConfig } from '../../utilities/Config';
|
||||
import RenderCustomComponent from '../../utilities/RenderCustomComponent';
|
||||
import LogOut from '../../icons/LogOut';
|
||||
|
||||
const baseClass = 'nav';
|
||||
|
||||
const DefaultLogout = () => {
|
||||
const config = useConfig();
|
||||
const {
|
||||
routes: { admin },
|
||||
admin: {
|
||||
logoutRoute,
|
||||
components: { logout }
|
||||
}
|
||||
} = config;
|
||||
return (
|
||||
<Link to={`${admin}${logoutRoute}`} className={`${baseClass}__log-out`}>
|
||||
<LogOut />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const Logout: React.FC = () => {
|
||||
const {
|
||||
admin: {
|
||||
components: {
|
||||
logout: { Button: CustomLogout } = {
|
||||
Button: undefined,
|
||||
},
|
||||
} = {},
|
||||
} = {},
|
||||
} = useConfig();
|
||||
|
||||
return (
|
||||
<RenderCustomComponent
|
||||
CustomComponent={CustomLogout}
|
||||
DefaultComponent={DefaultLogout}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Logout;
|
||||
@@ -4,7 +4,6 @@ import { useConfig } from '../../utilities/Config';
|
||||
import { useAuth } from '../../utilities/Auth';
|
||||
import RenderCustomComponent from '../../utilities/RenderCustomComponent';
|
||||
import Chevron from '../../icons/Chevron';
|
||||
import LogOut from '../../icons/LogOut';
|
||||
import Menu from '../../icons/Menu';
|
||||
import CloseMenu from '../../icons/CloseMenu';
|
||||
import Icon from '../../graphics/Icon';
|
||||
@@ -14,6 +13,7 @@ import NavGroup from '../NavGroup';
|
||||
import { groupNavItems, Group, EntityToGroup, EntityType } from '../../../utilities/groupNavItems';
|
||||
|
||||
import './index.scss';
|
||||
import Logout from '../Logout';
|
||||
|
||||
const baseClass = 'nav';
|
||||
|
||||
@@ -31,7 +31,7 @@ const DefaultNav = () => {
|
||||
admin: {
|
||||
components: {
|
||||
beforeNavLinks,
|
||||
afterNavLinks,
|
||||
afterNavLinks
|
||||
},
|
||||
},
|
||||
} = useConfig();
|
||||
@@ -137,12 +137,7 @@ const DefaultNav = () => {
|
||||
>
|
||||
<Account />
|
||||
</Link>
|
||||
<Link
|
||||
to={`${admin}/logout`}
|
||||
className={`${baseClass}__log-out`}
|
||||
>
|
||||
<LogOut />
|
||||
</Link>
|
||||
<Logout/>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,13 @@ const modalSlug = 'stay-logged-in';
|
||||
const StayLoggedInModal: React.FC<Props> = (props) => {
|
||||
const { refreshCookie } = props;
|
||||
const history = useHistory();
|
||||
const { routes: { admin } } = useConfig();
|
||||
const config = useConfig();
|
||||
const {
|
||||
routes: { admin },
|
||||
admin: {
|
||||
logoutRoute
|
||||
}
|
||||
} = config;
|
||||
const { toggleModal } = useModal();
|
||||
|
||||
return (
|
||||
@@ -31,7 +37,7 @@ const StayLoggedInModal: React.FC<Props> = (props) => {
|
||||
buttonStyle="secondary"
|
||||
onClick={() => {
|
||||
toggleModal(modalSlug);
|
||||
history.push(`${admin}/logout`);
|
||||
history.push(`${admin}${logoutRoute}`);
|
||||
}}
|
||||
>
|
||||
Log out
|
||||
|
||||
@@ -25,6 +25,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
const {
|
||||
admin: {
|
||||
user: userSlug,
|
||||
inactivityRoute: logoutInactivityRoute,
|
||||
},
|
||||
serverURL,
|
||||
routes: {
|
||||
@@ -57,7 +58,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
setUser(json.user);
|
||||
} else {
|
||||
setUser(null);
|
||||
push(`${admin}/logout-inactivity`);
|
||||
push(`${admin}${logoutInactivityRoute}`);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
@@ -145,7 +146,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
if (remainingTime > 0) {
|
||||
forceLogOut = setTimeout(() => {
|
||||
setUser(null);
|
||||
push(`${admin}/logout-inactivity`);
|
||||
push(`${admin}${logoutInactivityRoute}`);
|
||||
closeAllModals();
|
||||
}, Math.min(remainingTime * 1000, maxTimeoutTime));
|
||||
}
|
||||
|
||||
@@ -18,12 +18,15 @@ const baseClass = 'login';
|
||||
const Login: React.FC = () => {
|
||||
const history = useHistory();
|
||||
const { user, setToken } = useAuth();
|
||||
const config = useConfig();
|
||||
const {
|
||||
admin: {
|
||||
user: userSlug,
|
||||
logoutRoute,
|
||||
components: {
|
||||
beforeLogin,
|
||||
afterLogin,
|
||||
logout
|
||||
} = {},
|
||||
},
|
||||
serverURL,
|
||||
@@ -32,7 +35,7 @@ const Login: React.FC = () => {
|
||||
api,
|
||||
},
|
||||
collections,
|
||||
} = useConfig();
|
||||
} = config;
|
||||
|
||||
const collection = collections.find(({ slug }) => slug === userSlug);
|
||||
|
||||
@@ -56,7 +59,7 @@ const Login: React.FC = () => {
|
||||
<p>
|
||||
To log in with another user, you should
|
||||
{' '}
|
||||
<Link to={`${admin}/logout`}>log out</Link>
|
||||
<Link to={`${admin}${logoutRoute}`}>log out</Link>
|
||||
{' '}
|
||||
first.
|
||||
</p>
|
||||
|
||||
@@ -17,8 +17,9 @@ import HiddenInput from '../../forms/field-types/HiddenInput';
|
||||
const baseClass = 'reset-password';
|
||||
|
||||
const ResetPassword: React.FC = () => {
|
||||
const { admin: { user: userSlug }, serverURL, routes: { admin, api } } = useConfig();
|
||||
const { token } = useParams<{token?: string}>();
|
||||
const config = useConfig();
|
||||
const { admin: { user: userSlug, logoutRoute }, serverURL, routes: { admin, api } } = config;
|
||||
const { token } = useParams<{ token?: string }>();
|
||||
const history = useHistory();
|
||||
const { user, setToken } = useAuth();
|
||||
|
||||
@@ -43,7 +44,7 @@ const ResetPassword: React.FC = () => {
|
||||
<p>
|
||||
To log in with another user, you should
|
||||
{' '}
|
||||
<Link to={`${admin}/logout`}>log out</Link>
|
||||
<Link to={`${admin}${logoutRoute}`}>log out</Link>
|
||||
{' '}
|
||||
first.
|
||||
</p>
|
||||
|
||||
@@ -5,8 +5,13 @@ import Meta from '../../utilities/Meta';
|
||||
import MinimalTemplate from '../../templates/Minimal';
|
||||
|
||||
const Unauthorized: React.FC = () => {
|
||||
const { routes: { admin } } = useConfig();
|
||||
|
||||
const config = useConfig();
|
||||
const {
|
||||
routes: { admin },
|
||||
admin: {
|
||||
logoutRoute
|
||||
},
|
||||
} = config;
|
||||
return (
|
||||
<MinimalTemplate className="unauthorized">
|
||||
<Meta
|
||||
@@ -19,7 +24,7 @@ const Unauthorized: React.FC = () => {
|
||||
<br />
|
||||
<Button
|
||||
el="link"
|
||||
to={`${admin}/logout`}
|
||||
to={`${admin}${logoutRoute}`}
|
||||
>
|
||||
Log out
|
||||
</Button>
|
||||
|
||||
@@ -19,7 +19,8 @@ export const defaults: Config = {
|
||||
disable: false,
|
||||
indexHTML: path.resolve(__dirname, '../admin/index.html'),
|
||||
avatar: 'default',
|
||||
components: {},
|
||||
logoutRoute: '/logout',
|
||||
inactivityRoute: '/logout-inactivity',
|
||||
css: path.resolve(__dirname, '../admin/scss/custom.css'),
|
||||
dateFormat: 'MMMM do yyyy, h:mm a',
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { JSONDefinition } from 'graphql-scalars';
|
||||
import joi from 'joi';
|
||||
|
||||
const component = joi.alternatives().try(
|
||||
@@ -62,6 +63,8 @@ export default joi.object({
|
||||
joi.string(),
|
||||
component,
|
||||
),
|
||||
logoutRoute: joi.string(),
|
||||
inactivityRoute: joi.string(),
|
||||
components: joi.object()
|
||||
.keys({
|
||||
routes: joi.array()
|
||||
@@ -82,6 +85,9 @@ export default joi.object({
|
||||
beforeNavLinks: joi.array().items(component),
|
||||
afterNavLinks: joi.array().items(component),
|
||||
Nav: component,
|
||||
logout: joi.object({
|
||||
Button: component,
|
||||
}),
|
||||
views: joi.object({
|
||||
Dashboard: component,
|
||||
Account: component,
|
||||
|
||||
@@ -154,6 +154,8 @@ export type Config = {
|
||||
css?: string
|
||||
dateFormat?: string
|
||||
avatar?: 'default' | 'gravatar' | React.ComponentType<any>,
|
||||
logoutRoute?: string,
|
||||
inactivityRoute?: string,
|
||||
components?: {
|
||||
routes?: AdminRoute[]
|
||||
providers?: React.ComponentType<{ children: React.ReactNode }>[]
|
||||
@@ -164,6 +166,9 @@ export type Config = {
|
||||
beforeNavLinks?: React.ComponentType<any>[]
|
||||
afterNavLinks?: React.ComponentType<any>[]
|
||||
Nav?: React.ComponentType<any>
|
||||
logout?: {
|
||||
Button?: React.ComponentType<any>,
|
||||
}
|
||||
graphics?: {
|
||||
Icon?: React.ComponentType<any>
|
||||
Logo?: React.ComponentType<any>
|
||||
|
||||
23
test/admin/components/Logout/index.tsx
Normal file
23
test/admin/components/Logout/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { useConfig } from '../../../../src/admin/components/utilities/Config';
|
||||
import LogOut from '../../../../src/admin/components/icons/LogOut';
|
||||
|
||||
|
||||
const Logout: React.FC = () => {
|
||||
const config = useConfig();
|
||||
const {
|
||||
routes: {
|
||||
admin,
|
||||
},
|
||||
admin: {
|
||||
logoutRoute
|
||||
},
|
||||
} = config;
|
||||
return (
|
||||
<a href={`${admin}${logoutRoute}#custom`}>
|
||||
<LogOut />
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default Logout;
|
||||
@@ -8,6 +8,7 @@ import CustomDefaultRoute from './components/views/CustomDefault';
|
||||
import BeforeLogin from './components/BeforeLogin';
|
||||
import AfterNavLinks from './components/AfterNavLinks';
|
||||
import { slug, globalSlug } from './shared';
|
||||
import Logout from './components/Logout';
|
||||
|
||||
export interface Post {
|
||||
id: string;
|
||||
@@ -38,6 +39,9 @@ export default buildConfig({
|
||||
beforeLogin: [
|
||||
BeforeLogin,
|
||||
],
|
||||
logout: {
|
||||
Button: Logout,
|
||||
},
|
||||
afterNavLinks: [
|
||||
AfterNavLinks,
|
||||
],
|
||||
|
||||
@@ -15,6 +15,11 @@ require('@babel/register')({
|
||||
|
||||
const [testSuiteDir] = process.argv.slice(2);
|
||||
|
||||
if (!testSuiteDir) {
|
||||
console.error('ERROR: You must provide an argument for "testSuiteDir"');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const configPath = path.resolve(__dirname, testSuiteDir, 'config.ts');
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
|
||||
@@ -2,8 +2,8 @@ import express from 'express';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import payload from '../src';
|
||||
|
||||
require("dotenv").config();
|
||||
const expressApp = express();
|
||||
|
||||
const init = async () => {
|
||||
await payload.initAsync({
|
||||
secret: uuid(),
|
||||
|
||||
@@ -15,7 +15,7 @@ const suiteName = args[0];
|
||||
// Run all
|
||||
if (!suiteName || args[0].startsWith('-')) {
|
||||
const bail = args.includes('--bail');
|
||||
const files = glob.sync(`${path.resolve(__dirname)}/**/*e2e.spec.ts`);
|
||||
const files = glob.sync(`${path.resolve(__dirname).replace(/\\/g, '/')}/**/*e2e.spec.ts`);
|
||||
console.log(`\n\nExecuting all ${files.length} E2E tests...`);
|
||||
files.forEach((file) => {
|
||||
clearWebpackCache();
|
||||
|
||||
Reference in New Issue
Block a user