From 799370f753aa165fdc690bb703b6a173ef3f94d7 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Mon, 1 Apr 2024 17:30:49 -0400 Subject: [PATCH] fix(next): establishes pattern for preview urls (#5581) --- .../src/routes/rest/collections/preview.ts | 45 +++++++++ .../next/src/routes/rest/globals/preview.ts | 44 +++++++++ packages/next/src/routes/rest/index.ts | 24 ++++- .../src/admin/elements/PreviewButton.ts | 12 +-- .../src/admin/elements/PublishButton.ts | 2 +- .../payload/src/admin/elements/SaveButton.ts | 2 +- .../src/admin/elements/SaveDraftButton.ts | 2 +- packages/payload/src/admin/types.ts | 9 +- .../payload/src/collections/config/types.ts | 16 ++-- packages/payload/src/globals/config/types.ts | 16 ++-- .../src/elements/DocumentControls/index.tsx | 12 +-- .../ui/src/elements/PreviewButton/index.tsx | 93 ++++--------------- .../elements/PreviewButton/usePreviewURL.tsx | 70 ++++++++++++++ .../ComponentMap/buildComponentMap/index.tsx | 14 +-- .../ComponentMap/buildComponentMap/types.ts | 3 +- 15 files changed, 234 insertions(+), 130 deletions(-) create mode 100644 packages/next/src/routes/rest/collections/preview.ts create mode 100644 packages/next/src/routes/rest/globals/preview.ts create mode 100644 packages/ui/src/elements/PreviewButton/usePreviewURL.tsx diff --git a/packages/next/src/routes/rest/collections/preview.ts b/packages/next/src/routes/rest/collections/preview.ts new file mode 100644 index 000000000..a54e9e924 --- /dev/null +++ b/packages/next/src/routes/rest/collections/preview.ts @@ -0,0 +1,45 @@ +import httpStatus from 'http-status' +import { findByIDOperation } from 'payload/operations' +import { isNumber } from 'payload/utilities' + +import type { CollectionRouteHandlerWithID } from '../types.js' + +import { routeError } from '../routeError.js' + +export const preview: CollectionRouteHandlerWithID = async ({ id, collection, req }) => { + const { searchParams } = req + const depth = searchParams.get('depth') + + const result = await findByIDOperation({ + id, + collection, + depth: isNumber(depth) ? Number(depth) : undefined, + draft: searchParams.get('draft') === 'true', + req, + }) + + let previewURL: string + + const generatePreviewURL = req.payload.config.collections.find( + (config) => config.slug === collection.config.slug, + )?.admin?.preview + + if (typeof generatePreviewURL === 'function') { + try { + previewURL = await generatePreviewURL(result, { + locale: req.locale, + token: req.user?.token, + }) + } catch (err) { + routeError({ + collection, + err, + req, + }) + } + } + + return Response.json(previewURL, { + status: httpStatus.OK, + }) +} diff --git a/packages/next/src/routes/rest/globals/preview.ts b/packages/next/src/routes/rest/globals/preview.ts new file mode 100644 index 000000000..5e0662ccd --- /dev/null +++ b/packages/next/src/routes/rest/globals/preview.ts @@ -0,0 +1,44 @@ +import httpStatus from 'http-status' +import { findOneOperation } from 'payload/operations' +import { isNumber } from 'payload/utilities' + +import type { GlobalRouteHandler } from '../types.js' + +import { routeError } from '../routeError.js' + +export const preview: GlobalRouteHandler = async ({ globalConfig, req }) => { + const { searchParams } = req + const depth = searchParams.get('depth') + + const result = await findOneOperation({ + slug: globalConfig.slug, + depth: isNumber(depth) ? Number(depth) : undefined, + draft: searchParams.get('draft') === 'true', + globalConfig, + req, + }) + + let previewURL: string + + const generatePreviewURL = req.payload.config.globals.find( + (config) => config.slug === globalConfig.slug, + )?.admin?.preview + + if (typeof generatePreviewURL === 'function') { + try { + previewURL = await generatePreviewURL(result, { + locale: req.locale, + token: req.user?.token, + }) + } catch (err) { + routeError({ + err, + req, + }) + } + } + + return Response.json(previewURL, { + status: httpStatus.OK, + }) +} diff --git a/packages/next/src/routes/rest/index.ts b/packages/next/src/routes/rest/index.ts index 3825b05b9..a614c2a56 100644 --- a/packages/next/src/routes/rest/index.ts +++ b/packages/next/src/routes/rest/index.ts @@ -34,6 +34,7 @@ import { find } from './collections/find.js' import { findByID } from './collections/findByID.js' import { findVersionByID } from './collections/findVersionByID.js' import { findVersions } from './collections/findVersions.js' +import { preview as previewCollection } from './collections/preview.js' import { restoreVersion } from './collections/restoreVersion.js' import { update } from './collections/update.js' import { updateByID } from './collections/updateByID.js' @@ -42,6 +43,7 @@ import { docAccess as docAccessGlobal } from './globals/docAccess.js' import { findOne } from './globals/findOne.js' import { findVersionByID as findVersionByIdGlobal } from './globals/findVersionByID.js' import { findVersions as findVersionsGlobal } from './globals/findVersions.js' +import { preview as previewGlobal } from './globals/preview.js' import { restoreVersion as restoreVersionGlobal } from './globals/restoreVersion.js' import { update as updateGlobal } from './globals/update.js' import { routeError } from './routeError.js' @@ -60,6 +62,7 @@ const endpoints = { getFile, init, me, + preview: previewCollection, versions: findVersions, }, PATCH: { @@ -88,6 +91,7 @@ const endpoints = { 'doc-versions': findVersionsGlobal, 'doc-versions-by-id': findVersionByIdGlobal, findOne, + preview: previewGlobal, }, POST: { 'doc-access': docAccessGlobal, @@ -171,6 +175,7 @@ export const GET = endpoints: req.payload.config.endpoints, request, }) + if (disableEndpoints) return disableEndpoints collection = req.payload.collections?.[slug1] @@ -212,10 +217,16 @@ export const GET = if (slug2 === 'file') { // /:collection/file/:filename res = await endpoints.collection.GET.getFile({ collection, filename: slug3, req }) + } else if (slug3 in endpoints.collection.GET) { + // /:collection/:id/preview + res = await (endpoints.collection.GET[slug3] as CollectionRouteHandlerWithID)({ + id: slug2, + collection, + req, + }) } else if (`doc-${slug2}-by-id` in endpoints.collection.GET) { // /:collection/access/:id // /:collection/versions/:id - res = await ( endpoints.collection.GET[`doc-${slug2}-by-id`] as CollectionRouteHandlerWithID )({ id: slug3, collection, req }) @@ -229,6 +240,7 @@ export const GET = endpoints: globalConfig.endpoints, request, }) + if (disableEndpoints) return disableEndpoints const customEndpointResponse = await handleCustomEndpoints({ @@ -236,6 +248,7 @@ export const GET = entitySlug: `${slug1}/${slug2}`, payloadRequest: req, }) + if (customEndpointResponse) return customEndpointResponse switch (slug.length) { @@ -244,9 +257,16 @@ export const GET = res = await endpoints.global.GET.findOne({ globalConfig, req }) break case 3: - if (`doc-${slug3}` in endpoints.global.GET) { + if (slug3 in endpoints.global.GET) { + // /globals/:slug/preview + res = await (endpoints.global.GET[slug3] as GlobalRouteHandler)({ + globalConfig, + req, + }) + } else if (`doc-${slug3}` in endpoints.global.GET) { // /globals/:slug/access // /globals/:slug/versions + // /globals/:slug/preview res = await (endpoints.global.GET?.[`doc-${slug3}`] as GlobalRouteHandler)({ globalConfig, req, diff --git a/packages/payload/src/admin/elements/PreviewButton.ts b/packages/payload/src/admin/elements/PreviewButton.ts index ced75b856..3763db684 100644 --- a/packages/payload/src/admin/elements/PreviewButton.ts +++ b/packages/payload/src/admin/elements/PreviewButton.ts @@ -1,11 +1 @@ -export type CustomPreviewButtonProps = React.ComponentType< - DefaultPreviewButtonProps & { - DefaultButton: React.ComponentType - } -> - -export type DefaultPreviewButtonProps = { - disabled: boolean - label: string - preview: () => void -} +export type CustomPreviewButton = React.ComponentType diff --git a/packages/payload/src/admin/elements/PublishButton.ts b/packages/payload/src/admin/elements/PublishButton.ts index ae18e2bb3..40fb78c03 100644 --- a/packages/payload/src/admin/elements/PublishButton.ts +++ b/packages/payload/src/admin/elements/PublishButton.ts @@ -1 +1 @@ -export type CustomPublishButtonProps = React.ComponentType +export type CustomPublishButton = React.ComponentType diff --git a/packages/payload/src/admin/elements/SaveButton.ts b/packages/payload/src/admin/elements/SaveButton.ts index db78e8464..81b232cff 100644 --- a/packages/payload/src/admin/elements/SaveButton.ts +++ b/packages/payload/src/admin/elements/SaveButton.ts @@ -1 +1 @@ -export type CustomSaveButtonProps = React.ComponentType +export type CustomSaveButton = React.ComponentType diff --git a/packages/payload/src/admin/elements/SaveDraftButton.ts b/packages/payload/src/admin/elements/SaveDraftButton.ts index 52d1662d6..6e7c04b99 100644 --- a/packages/payload/src/admin/elements/SaveDraftButton.ts +++ b/packages/payload/src/admin/elements/SaveDraftButton.ts @@ -1 +1 @@ -export type CustomSaveDraftButtonProps = React.ComponentType +export type CustomSaveDraftButton = React.ComponentType diff --git a/packages/payload/src/admin/types.ts b/packages/payload/src/admin/types.ts index 42c66d675..ff055c7ed 100644 --- a/packages/payload/src/admin/types.ts +++ b/packages/payload/src/admin/types.ts @@ -2,11 +2,10 @@ export type { RichTextAdapter, RichTextFieldProps } from './RichText.js' export type { CellComponentProps, DefaultCellComponentProps } from './elements/Cell.js' export type { ConditionalDateProps } from './elements/DatePicker.js' export type { DayPickerProps, SharedProps, TimePickerProps } from './elements/DatePicker.js' -export type { DefaultPreviewButtonProps } from './elements/PreviewButton.js' -export type { CustomPreviewButtonProps } from './elements/PreviewButton.js' -export type { CustomPublishButtonProps } from './elements/PublishButton.js' -export type { CustomSaveButtonProps } from './elements/SaveButton.js' -export type { CustomSaveDraftButtonProps } from './elements/SaveDraftButton.js' +export type { CustomPreviewButton } from './elements/PreviewButton.js' +export type { CustomPublishButton } from './elements/PublishButton.js' +export type { CustomSaveButton } from './elements/SaveButton.js' +export type { CustomSaveDraftButton } from './elements/SaveDraftButton.js' export type { DocumentTab, DocumentTabComponent, diff --git a/packages/payload/src/collections/config/types.ts b/packages/payload/src/collections/config/types.ts index 0bc76e44f..d9d594717 100644 --- a/packages/payload/src/collections/config/types.ts +++ b/packages/payload/src/collections/config/types.ts @@ -2,10 +2,10 @@ import type { GraphQLInputObjectType, GraphQLNonNull, GraphQLObjectType } from ' import type { DeepRequired } from 'ts-essentials' import type { - CustomPreviewButtonProps, - CustomPublishButtonProps, - CustomSaveButtonProps, - CustomSaveDraftButtonProps, + CustomPreviewButton, + CustomPublishButton, + CustomSaveButton, + CustomSaveDraftButton, } from '../../admin/types.js' import type { Auth, ClientUser, IncomingAuthType } from '../../auth/types.js' import type { @@ -211,23 +211,23 @@ export type CollectionAdminOptions = { /** * Replaces the "Preview" button */ - PreviewButton?: CustomPreviewButtonProps + PreviewButton?: CustomPreviewButton /** * Replaces the "Publish" button * + drafts must be enabled */ - PublishButton?: CustomPublishButtonProps + PublishButton?: CustomPublishButton /** * Replaces the "Save" button * + drafts must be disabled */ - SaveButton?: CustomSaveButtonProps + SaveButton?: CustomSaveButton /** * Replaces the "Save Draft" button * + drafts must be enabled * + autosave must be disabled */ - SaveDraftButton?: CustomSaveDraftButtonProps + SaveDraftButton?: CustomSaveDraftButton } views?: { /** diff --git a/packages/payload/src/globals/config/types.ts b/packages/payload/src/globals/config/types.ts index 2e7580833..cd6bbb87a 100644 --- a/packages/payload/src/globals/config/types.ts +++ b/packages/payload/src/globals/config/types.ts @@ -2,10 +2,10 @@ import type { GraphQLNonNull, GraphQLObjectType } from 'graphql' import type { DeepRequired } from 'ts-essentials' import type { - CustomPreviewButtonProps, - CustomPublishButtonProps, - CustomSaveButtonProps, - CustomSaveDraftButtonProps, + CustomPreviewButton, + CustomPublishButton, + CustomSaveButton, + CustomSaveDraftButton, } from '../../admin/types.js' import type { User } from '../../auth/types.js' import type { @@ -79,23 +79,23 @@ export type GlobalAdminOptions = { /** * Replaces the "Preview" button */ - PreviewButton?: CustomPreviewButtonProps + PreviewButton?: CustomPreviewButton /** * Replaces the "Publish" button * + drafts must be enabled */ - PublishButton?: CustomPublishButtonProps + PublishButton?: CustomPublishButton /** * Replaces the "Save" button * + drafts must be disabled */ - SaveButton?: CustomSaveButtonProps + SaveButton?: CustomSaveButton /** * Replaces the "Save Draft" button * + drafts must be enabled * + autosave must be disabled */ - SaveDraftButton?: CustomSaveDraftButtonProps + SaveDraftButton?: CustomSaveDraftButton } views?: { /** diff --git a/packages/ui/src/elements/DocumentControls/index.tsx b/packages/ui/src/elements/DocumentControls/index.tsx index f8b57898e..e20d9f72b 100644 --- a/packages/ui/src/elements/DocumentControls/index.tsx +++ b/packages/ui/src/elements/DocumentControls/index.tsx @@ -154,15 +154,9 @@ export const DocumentControls: React.FC<{
- {/* {(collectionConfig?.admin?.preview || globalConfig?.admin?.preview) && ( - - )} */} + {componentMap?.isPreviewEnabled && ( + + )} {hasSavePermission && ( {collectionConfig?.versions?.drafts || globalConfig?.versions?.drafts ? ( diff --git a/packages/ui/src/elements/PreviewButton/index.tsx b/packages/ui/src/elements/PreviewButton/index.tsx index 357e2d0ad..fb7dd3fe1 100644 --- a/packages/ui/src/elements/PreviewButton/index.tsx +++ b/packages/ui/src/elements/PreviewButton/index.tsx @@ -1,31 +1,24 @@ 'use client' -import type { GeneratePreviewURL } from 'payload/config' -import type { CustomPreviewButtonProps, DefaultPreviewButtonProps } from 'payload/types' +import React from 'react' -import React, { useCallback, useRef, useState } from 'react' -import { toast } from 'react-toastify' - -import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js' -import { useAuth } from '../../providers/Auth/index.js' -import { useConfig } from '../../providers/Config/index.js' -import { useDocumentInfo } from '../../providers/DocumentInfo/index.js' -import { useLocale } from '../../providers/Locale/index.js' -import { useTranslation } from '../../providers/Translation/index.js' import { Button } from '../Button/index.js' +import { usePreviewURL } from './usePreviewURL.js' const baseClass = 'preview-btn' -const DefaultPreviewButton: React.FC = ({ - disabled, - label, - preview, -}) => { +const DefaultPreviewButton: React.FC = () => { + const { generatePreviewURL, label } = usePreviewURL() + return (