feat: abstracts revisions components for reuse in globals

This commit is contained in:
James
2021-12-01 12:21:56 -05:00
parent 1920a937b2
commit da5684df27
21 changed files with 364 additions and 211 deletions

View File

@@ -9,7 +9,7 @@ import { requests } from '../api';
import Loading from './elements/Loading';
import StayLoggedIn from './modals/StayLoggedIn';
import Unlicensed from './views/Unlicensed';
import Revisions from './views/collections/Revisions';
import Revisions from './views/Revisions';
const Dashboard = lazy(() => import('./views/Dashboard'));
const ForgotPassword = lazy(() => import('./views/ForgotPassword'));
@@ -117,70 +117,65 @@ const Routes = () => {
<Account />
</Route>
{collections.map((collection) => (
<Route
key={`${collection.slug}-list`}
path={`${match.url}/collections/${collection.slug}`}
exact
render={(routeProps) => {
if (permissions?.collections?.[collection.slug]?.read?.permission) {
return (
<List
{...routeProps}
collection={collection}
/>
);
}
{collections.reduce((collectionRoutes, collection) => {
const routesToReturn = [
...collectionRoutes,
<Route
key={`${collection.slug}-list`}
path={`${match.url}/collections/${collection.slug}`}
exact
render={(routeProps) => {
if (permissions?.collections?.[collection.slug]?.read?.permission) {
return (
<List
{...routeProps}
collection={collection}
/>
);
}
return <Unauthorized />;
}}
/>
))}
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 (
<Edit
{...routeProps}
collection={collection}
/>
);
}
{collections.map((collection) => (
<Route
key={`${collection.slug}-create`}
path={`${match.url}/collections/${collection.slug}/create`}
exact
render={(routeProps) => {
if (permissions?.collections?.[collection.slug]?.create?.permission) {
return (
<Edit
{...routeProps}
collection={collection}
/>
);
}
return <Unauthorized />;
}}
/>,
<Route
key={`${collection.slug}-edit`}
path={`${match.url}/collections/${collection.slug}/:id`}
exact
render={(routeProps) => {
if (permissions?.collections?.[collection.slug]?.read?.permission) {
return (
<Edit
isEditing
{...routeProps}
collection={collection}
/>
);
}
return <Unauthorized />;
}}
/>
))}
return <Unauthorized />;
}}
/>,
];
{collections.map((collection) => (
<Route
key={`${collection.slug}-edit`}
path={`${match.url}/collections/${collection.slug}/:id`}
exact
render={(routeProps) => {
if (permissions?.collections?.[collection.slug]?.read?.permission) {
return (
<Edit
isEditing
{...routeProps}
collection={collection}
/>
);
}
return <Unauthorized />;
}}
/>
))}
{collections.map((collection) => {
if (collection.revisions) {
return (
routesToReturn.push(
<Route
key={`${collection.slug}-revisions`}
path={`${match.url}/collections/${collection.slug}/:id/revisions`}
@@ -197,16 +192,10 @@ const Routes = () => {
return <Unauthorized />;
}}
/>
/>,
);
}
return null;
})}
{collections.map((collection) => {
if (collection.revisions) {
return (
routesToReturn.push(
<Route
key={`${collection.slug}-view-revision`}
path={`${match.url}/collections/${collection.slug}/:id/revisions/:revisionID`}
@@ -218,32 +207,72 @@ const Routes = () => {
return <Unauthorized />;
}}
/>
/>,
);
}
return null;
})}
return routesToReturn;
}, [])}
{globals && globals.map((global) => (
<Route
key={`${global.slug}`}
path={`${match.url}/globals/${global.slug}`}
exact
render={(routeProps) => {
if (permissions?.globals?.[global.slug]?.read?.permission) {
return (
<EditGlobal
{...routeProps}
global={global}
/>
);
}
{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 (
<EditGlobal
{...routeProps}
global={global}
/>
);
}
return <Unauthorized />;
}}
/>
))}
return <Unauthorized />;
}}
/>,
];
if (global.revisions) {
routesToReturn.push(
<Route
key={`${global.slug}-revisions`}
path={`${match.url}/globals/${global.slug}/revisions`}
exact
render={(routeProps) => {
if (permissions?.globals?.[global.slug]?.readRevisions?.permission) {
return (
<Revisions
{...routeProps}
global={global}
/>
);
}
return <Unauthorized />;
}}
/>,
);
routesToReturn.push(
<Route
key={`${global.slug}-view-revision`}
path={`${match.url}/globals/${global.slug}/revisions/:revisionID`}
exact
render={(routeProps) => {
if (permissions?.globals?.[global.slug]?.readRevisions?.permission) {
return null;
}
return <Unauthorized />;
}}
/>,
);
}
return routesToReturn;
}, [])}
<Route path={`${match.url}*`}>
<NotFound />

View File

@@ -1,4 +1,4 @@
@import '../../../../../scss/styles.scss';
@import '../../../scss/styles.scss';
.revisions-count__button {
font-weight: 600;

View File

@@ -1,24 +1,40 @@
import { useConfig } from '@payloadcms/config-provider';
import React, { useEffect } from 'react';
import Button from '../../../../elements/Button';
import usePayloadAPI from '../../../../../hooks/usePayloadAPI';
import Button from '../Button';
import usePayloadAPI from '../../../hooks/usePayloadAPI';
import { Props } from './types';
import './index.scss';
const baseClass = 'revisions-count';
const Revisions: React.FC<Props> = ({ collection, id, submissionCount }) => {
const Revisions: React.FC<Props> = ({ collection, global, id, submissionCount }) => {
const { serverURL, routes: { admin } } = useConfig();
const [{ data, isLoading }, { setParams }] = usePayloadAPI(`${serverURL}/api/${collection.slug}/revisions`, {
initialParams: {
let initialParams;
let fetchURL: string;
let revisionsURL: string;
if (collection) {
initialParams = {
where: {
parent: {
equals: id,
},
},
},
};
fetchURL = `${serverURL}/api/${collection.slug}/revisions`;
revisionsURL = `${admin}/collections/${collection.slug}/${id}/revisions`;
}
if (global) {
fetchURL = `${serverURL}/api/globals/${global.slug}/revisions`;
revisionsURL = `${admin}/globals/${global.slug}/revisions`;
}
const [{ data, isLoading }, { setParams }] = usePayloadAPI(fetchURL, {
initialParams,
});
useEffect(() => {
@@ -51,7 +67,7 @@ const Revisions: React.FC<Props> = ({ collection, id, submissionCount }) => {
className={`${baseClass}__button`}
buttonStyle="none"
el="link"
to={`${admin}/collections/${collection.slug}/${id}/revisions`}
to={revisionsURL}
>
{data.docs.length}
{' '}

View File

@@ -0,0 +1,9 @@
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
import { SanitizedGlobalConfig } from '../../../../globals/config/types';
export type Props = {
collection?: SanitizedCollectionConfig,
global?: SanitizedGlobalConfig
id?: string | number
submissionCount: number
}

View File

@@ -10,6 +10,7 @@ import CopyToClipboard from '../../elements/CopyToClipboard';
import Meta from '../../utilities/Meta';
import fieldTypes from '../../forms/field-types';
import LeaveWithoutSaving from '../../modals/LeaveWithoutSaving';
import RevisionsCount from '../../elements/RevisionsCount';
import { Props } from './types';
import './index.scss';
@@ -20,12 +21,13 @@ const baseClass = 'global-edit';
const DefaultGlobalView: React.FC<Props> = (props) => {
const { admin: { dateFormat } } = useConfig();
const {
global, data, onSave, permissions, action, apiURL, initialState,
global, data, onSave, permissions, action, apiURL, initialState, submissionCount,
} = props;
const {
fields,
preview,
revisions,
label,
admin: {
description,
@@ -99,6 +101,15 @@ const DefaultGlobalView: React.FC<Props> = (props) => {
</div>
{data && (
<ul className={`${baseClass}__meta`}>
{revisions && (
<li>
<div className={`${baseClass}__label`}>Revisions</div>
<RevisionsCount
submissionCount={submissionCount}
global={global}
/>
</li>
)}
{data && (
<li className={`${baseClass}__api-url`}>
<span className={`${baseClass}__label`}>

View File

@@ -15,16 +15,15 @@ import { IndexProps } from './types';
const GlobalView: React.FC<IndexProps> = (props) => {
const { state: locationState } = useLocation<{data?: Record<string, unknown>}>();
const history = useHistory();
const locale = useLocale();
const { setStepNav } = useStepNav();
const { permissions } = useAuth();
const [initialState, setInitialState] = useState({});
const [submissionCount, setSubmissionCount] = useState(0);
const {
serverURL,
routes: {
admin,
api,
},
} = useConfig();
@@ -44,14 +43,10 @@ const GlobalView: React.FC<IndexProps> = (props) => {
} = {},
} = global;
const onSave = (json) => {
history.push(`${admin}/globals/${global.slug}`, {
status: {
message: json.message,
type: 'success',
},
data: json.doc,
});
const onSave = async (json) => {
const state = await buildStateFromSchema(fields, json.doc);
setInitialState(state);
setSubmissionCount((count) => count + 1);
};
const [{ data }] = usePayloadAPI(
@@ -97,6 +92,7 @@ const GlobalView: React.FC<IndexProps> = (props) => {
onSave,
apiURL: `${serverURL}${api}/globals/${slug}?depth=0`,
action: `${serverURL}${api}/globals/${slug}?locale=${locale}&depth=0&fallback-locale=null`,
submissionCount,
}}
/>
</NegativeFieldGutterProvider>

View File

@@ -14,4 +14,5 @@ export type Props = {
action: string
apiURL: string
initialState: Fields
submissionCount: number
}

View File

@@ -2,22 +2,29 @@ import React from 'react';
import { Link, useRouteMatch } from 'react-router-dom';
import { useConfig } from '@payloadcms/config-provider';
import format from 'date-fns/format';
import { Column } from '../../../elements/Table/types';
import SortColumn from '../../../elements/SortColumn';
import { SanitizedCollectionConfig } from '../../../../../collections/config/types';
import { Column } from '../../elements/Table/types';
import SortColumn from '../../elements/SortColumn';
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
import { SanitizedGlobalConfig } from '../../../../globals/config/types';
type CreatedAtCellProps = {
id: string
date: string
collection: SanitizedCollectionConfig
collection?: SanitizedCollectionConfig
global?: SanitizedGlobalConfig
}
const CreatedAtCell: React.FC<CreatedAtCellProps> = ({ collection, id, date }) => {
const CreatedAtCell: React.FC<CreatedAtCellProps> = ({ collection, global, id, date }) => {
const { routes: { admin }, admin: { dateFormat } } = useConfig();
const { params: { id: docID } } = useRouteMatch<{ id: string }>();
let to: string;
if (collection) to = `${admin}/collections/${collection.slug}/${docID}/revisions/${id}`;
if (global) to = `${admin}/globals/${global.slug}/revisions/${id}`;
return (
<Link to={`${admin}/collections/${collection.slug}/${docID}/revisions/${id}`}>
<Link to={to}>
{date && format(new Date(date), dateFormat)}
</Link>
);
@@ -29,7 +36,7 @@ const IDCell: React.FC = ({ children }) => (
</span>
);
export const getColumns = (collection: SanitizedCollectionConfig): Column[] => [
export const getColumns = (collection: SanitizedCollectionConfig, global: SanitizedGlobalConfig): Column[] => [
{
accessor: 'createdAt',
components: {
@@ -42,6 +49,7 @@ export const getColumns = (collection: SanitizedCollectionConfig): Column[] => [
renderCell: (row, data) => (
<CreatedAtCell
collection={collection}
global={global}
id={row?.id}
date={data}
/>
@@ -53,7 +61,7 @@ export const getColumns = (collection: SanitizedCollectionConfig): Column[] => [
components: {
Heading: (
<SortColumn
label="ID"
label="Revision ID"
disable
name="id"
/>

View File

@@ -1,6 +1,6 @@
@import '../../../../scss/styles';
@import '../../../scss/styles.scss';
.collection-revisions {
.revisions {
width: 100%;
margin-bottom: base(2);
@@ -14,6 +14,10 @@
margin-bottom: $baseline;
}
&__intro {
margin-bottom: base(.5);
}
.table {
table {
width: 100%;

View File

@@ -1,55 +1,85 @@
import { useConfig } from '@payloadcms/config-provider';
import React, { useEffect, useState } from 'react';
import { useRouteMatch } from 'react-router-dom';
import usePayloadAPI from '../../../../hooks/usePayloadAPI';
import Eyebrow from '../../../elements/Eyebrow';
import Loading from '../../../elements/Loading';
import { useStepNav } from '../../../elements/StepNav';
import { StepNavItem } from '../../../elements/StepNav/types';
import Meta from '../../../utilities/Meta';
import usePayloadAPI from '../../../hooks/usePayloadAPI';
import Eyebrow from '../../elements/Eyebrow';
import Loading from '../../elements/Loading';
import { useStepNav } from '../../elements/StepNav';
import { StepNavItem } from '../../elements/StepNav/types';
import Meta from '../../utilities/Meta';
import { Props } from './types';
import IDLabel from '../../../elements/IDLabel';
import IDLabel from '../../elements/IDLabel';
import { getColumns } from './columns';
import Table from '../../../elements/Table';
import Paginator from '../../../elements/Paginator';
import PerPage from '../../../elements/PerPage';
import Table from '../../elements/Table';
import Paginator from '../../elements/Paginator';
import PerPage from '../../elements/PerPage';
import { useSearchParams } from '../../utilities/SearchParams';
import './index.scss';
import { useSearchParams } from '../../../utilities/SearchParams';
const baseClass = 'collection-revisions';
const baseClass = 'revisions';
const Revisions: React.FC<Props> = ({ collection }) => {
const Revisions: React.FC<Props> = ({ collection, global }) => {
const { serverURL, routes: { admin, api } } = useConfig();
const { setStepNav } = useStepNav();
const { params: { id } } = useRouteMatch<{ id: string }>();
const [tableColumns] = useState(() => getColumns(collection));
const [tableColumns] = useState(() => getColumns(collection, global));
const [fetchURL, setFetchURL] = useState('');
const { page, sort, limit } = useSearchParams();
const [{ data: doc }] = usePayloadAPI(`${serverURL}/api/${collection.slug}/${id}`);
let docURL: string;
let entityLabel: string;
let slug: string;
if (collection) {
({ slug } = collection);
docURL = `${serverURL}/api/${slug}/${id}`;
entityLabel = collection.labels.singular;
}
if (global) {
({ slug } = global);
docURL = `${serverURL}/api/globals/${slug}`;
entityLabel = global.label;
}
const useAsTitle = collection?.admin?.useAsTitle || 'id';
const [{ data: doc }] = usePayloadAPI(docURL);
const [{ data: revisionsData, isLoading: isLoadingRevisions }, { setParams }] = usePayloadAPI(fetchURL);
const useAsTitle = collection.admin.useAsTitle || 'id';
useEffect(() => {
const nav: StepNavItem[] = [
{
url: `${admin}/collections/${collection.slug}`,
label: collection.labels.plural,
},
{
label: doc ? doc[useAsTitle] : '',
url: `${admin}/collections/${collection.slug}/${id}`,
},
{
label: 'Revisions',
},
];
let nav: StepNavItem[] = [];
if (collection) {
nav = [
{
url: `${admin}/collections/${collection.slug}`,
label: collection.labels.plural,
},
{
label: doc ? doc[useAsTitle] : '',
url: `${admin}/collections/${collection.slug}/${id}`,
},
{
label: 'Revisions',
},
];
}
if (global) {
nav = [
{
url: `${admin}/globals/${global.slug}`,
label: global.label,
},
{
label: 'Revisions',
},
];
}
setStepNav(nav);
}, [setStepNav, collection, useAsTitle, doc, admin, id]);
}, [setStepNav, collection, global, useAsTitle, doc, admin, id]);
useEffect(() => {
const params = {
@@ -70,29 +100,43 @@ const Revisions: React.FC<Props> = ({ collection }) => {
// Performance enhancement
// Setting the Fetch URL this way
// prevents a double-fetch
setFetchURL(`${serverURL}${api}/${collection.slug}/revisions`);
setFetchURL(`${serverURL}${api}/${slug}/revisions`);
setParams(params);
}, [setParams, page, sort, collection, limit, serverURL, api, id]);
}, [setParams, page, sort, slug, limit, serverURL, api, id]);
const useIDLabel = doc[useAsTitle] === doc?.id;
let heading: string;
let metaDesc: string;
if (collection) {
metaDesc = `Viewing revisions for the ${entityLabel} ${doc[useAsTitle]}`;
heading = doc?.[useAsTitle];
}
if (global) {
metaDesc = `Viewing revisions for the global ${entityLabel}`;
heading = entityLabel;
}
return (
<div className={baseClass}>
<Meta
title={`Revisions - ${doc[useAsTitle]} - ${collection.labels.singular}`}
description={`Viewing revisions for the ${collection.labels.singular} ${doc[useAsTitle]}`}
keywords={`Revisions, ${collection.labels.singular}, Payload, CMS`}
title={`Revisions - ${doc[useAsTitle]} - ${entityLabel}`}
description={metaDesc}
keywords={`Revisions, ${entityLabel}, Payload, CMS`}
/>
<Eyebrow />
<div className={`${baseClass}__wrap`}>
<header className={`${baseClass}__header`}>
Showing revisions for:
<div className={`${baseClass}__intro`}>Showing revisions for:</div>
{useIDLabel && (
<IDLabel id={doc?.id} />
)}
{!useIDLabel && (
<h1>
{doc[useAsTitle]}
{heading}
</h1>
)}
</header>

View File

@@ -0,0 +1,7 @@
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
import { SanitizedGlobalConfig } from '../../../../globals/config/types';
export type Props = {
collection?: SanitizedCollectionConfig
global?: SanitizedGlobalConfig
}

View File

@@ -16,7 +16,7 @@ import fieldTypes from '../../../forms/field-types';
import RenderTitle from '../../../elements/RenderTitle';
import LeaveWithoutSaving from '../../../modals/LeaveWithoutSaving';
import Auth from './Auth';
import Revisions from './Revisions';
import RevisionsCount from '../../../elements/RevisionsCount';
import Upload from './Upload';
import { Props } from './types';
@@ -192,7 +192,7 @@ const DefaultEditView: React.FC<Props> = (props) => {
{revisions && (
<li>
<div className={`${baseClass}__label`}>Revisions</div>
<Revisions
<RevisionsCount
submissionCount={submissionCount}
collection={collection}
id={id}

View File

@@ -1,7 +0,0 @@
import { SanitizedCollectionConfig } from '../../../../../../collections/config/types';
export type Props = {
collection: SanitizedCollectionConfig,
id: string | number
submissionCount: number
}

View File

@@ -1,5 +0,0 @@
import { SanitizedCollectionConfig } from '../../../../../collections/config/types';
export type Props = {
collection: SanitizedCollectionConfig
}