From 820af18fdfe73deb162ad92cb40ad9d8bf0d56e4 Mon Sep 17 00:00:00 2001 From: Mike Decker Date: Thu, 28 Mar 2024 22:15:20 -0700 Subject: [PATCH] Replace next Draft Mode with custom cookie and preview route --- app/[...slug]/page.tsx | 32 ++---------------- app/api/draft/disable/route.tsx | 9 ----- app/api/draft/route.tsx | 13 +++++--- app/layout.tsx | 33 +++++-------------- app/page.tsx | 4 +-- app/preview/[...slug]/page.tsx | 32 ++++++++++++++++++ .../elements/drupal-window-sync.tsx | 27 +++++++-------- src/components/elements/user-analytics.tsx | 18 ++++++++++ src/components/global/page-header.tsx | 4 +-- src/components/layouts/interior-page.tsx | 4 +-- src/components/nodes/cards/node-card.tsx | 6 ++-- .../nodes/list-item/node-list-item.tsx | 6 ++-- src/components/nodes/pages/node-page.tsx | 6 ++-- .../stanford-course/stanford-course-page.tsx | 4 +-- .../stanford-event/stanford-event-page.tsx | 4 +-- .../stanford-news/stanford-news-page.tsx | 4 +-- src/components/paragraphs/paragraph.tsx | 6 ++-- src/components/paragraphs/rows/one-column.tsx | 4 +-- .../paragraphs/rows/three-column.tsx | 4 +-- src/components/paragraphs/rows/two-column.tsx | 4 +-- src/lib/drupal/get-access-token.tsx | 4 +-- src/lib/drupal/utils.tsx | 25 ++++++++++---- src/lib/gql/gql-queries.tsx | 8 ++--- 23 files changed, 140 insertions(+), 121 deletions(-) delete mode 100644 app/api/draft/disable/route.tsx create mode 100644 app/preview/[...slug]/page.tsx create mode 100644 src/components/elements/user-analytics.tsx diff --git a/app/[...slug]/page.tsx b/app/[...slug]/page.tsx index f71fb39..fde7eb7 100644 --- a/app/[...slug]/page.tsx +++ b/app/[...slug]/page.tsx @@ -1,11 +1,10 @@ import NodePage from "@components/nodes/pages/node-page"; -import UnpublishedBanner from "@components/elements/unpublished-banner"; import {Metadata} from "next"; import {NodeUnion} from "@lib/gql/__generated__/drupal.d"; import {getAllNodePaths, getEntityFromPath} from "@lib/gql/gql-queries"; import {getNodeMetadata} from "./metadata"; -import {isDraftMode} from "@lib/drupal/utils"; import {notFound, redirect} from "next/navigation"; +import {getPathFromContext, PageProps} from "@lib/drupal/utils"; // https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config export const revalidate = false; @@ -13,28 +12,17 @@ export const dynamic = 'force-static'; const Page = async ({params}: PageProps) => { const path = getPathFromContext({params}) - const inDraft = isDraftMode(); - const {redirect: redirectPath, entity, error} = await getEntityFromPath(path, inDraft) + const {redirect: redirectPath, entity, error} = await getEntityFromPath(path) if (error) throw new Error(error); if (redirectPath?.url) redirect(redirectPath.url) if (!entity) notFound(); - return ( - <> - - Unpublished Page - - - - ) + return } export const generateMetadata = async ({params}: PageProps): Promise => { - // If the user is in draft mode, there's no need to emit any customized metadata. - if (isDraftMode()) return {}; - const path = getPathFromContext({params}) const {entity} = await getEntityFromPath(path) return entity ? getNodeMetadata(entity) : {}; @@ -46,18 +34,4 @@ export const generateStaticParams = async (): Promise => return nodePaths.map(path => ({slug: path.split('/').filter(part => !!part)})); } -const getPathFromContext = (context: PageProps, prefix = ""): string => { - let {slug} = context.params - - slug = Array.isArray(slug) ? slug.map((s) => encodeURIComponent(s)).join("/") : slug - slug = slug.replace(/^\//, ''); - return prefix ? `${prefix}/${slug}` : `/${slug}` -} - -type PageProps = { - params: { slug: string | string[] } - searchParams?: Record -} - - export default Page; \ No newline at end of file diff --git a/app/api/draft/disable/route.tsx b/app/api/draft/disable/route.tsx deleted file mode 100644 index 8b38373..0000000 --- a/app/api/draft/disable/route.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import {NextResponse} from "next/server"; -import {draftMode} from 'next/headers' - -export const revalidate = 0; - -export async function GET() { - draftMode().disable() - return NextResponse.json({disabled: true}); -} \ No newline at end of file diff --git a/app/api/draft/route.tsx b/app/api/draft/route.tsx index 77ff042..e0d78e7 100644 --- a/app/api/draft/route.tsx +++ b/app/api/draft/route.tsx @@ -1,6 +1,6 @@ import {NextRequest, NextResponse} from "next/server"; -import {draftMode} from 'next/headers' import {redirect} from 'next/navigation' +import {cookies} from "next/headers"; export const revalidate = 0; @@ -18,10 +18,15 @@ export async function GET(request: NextRequest) { if (!slug) { return NextResponse.json({message: 'Invalid slug path'}, {status: 401}) } - - draftMode().enable() + cookies().set('preview', secret, { + maxAge: 60 * 60, + httpOnly: true, + sameSite: 'none', + secure: true, + partitioned: true, + }); // Redirect to the path from the fetched post // We don't redirect to searchParams.slug as that might lead to open redirect vulnerabilities - redirect(slug) + redirect(`/preview/${slug}`) } \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index a333494..dc5ec28 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,17 +1,12 @@ import '../src/styles/index.css'; import BackToTop from "@components/elements/back-to-top"; -import DrupalWindowSync from "@components/elements/drupal-window-sync"; -import Editori11y from "@components/tools/editorially"; -import Link from "@components/elements/link"; import PageFooter from "@components/global/page-footer"; import PageHeader from "@components/global/page-header"; -import Script from "next/script"; -import {GoogleAnalytics} from "@next/third-parties/google"; import {Icon} from "next/dist/lib/metadata/types/metadata-types"; -import {StanfordBasicSiteSetting} from "@lib/gql/__generated__/drupal.d"; -import {getConfigPage} from "@lib/gql/gql-queries"; -import {isDraftMode} from "@lib/drupal/utils"; import {sourceSans3} from "../src/styles/fonts"; +import DrupalWindowSync from "@components/elements/drupal-window-sync"; +import {isPreviewMode} from "@lib/drupal/utils"; +import UserAnalytics from "@components/elements/user-analytics"; const appleIcons: Icon[] = [60, 72, 76, 114, 120, 144, 152, 180].map(size => ({ url: `https://www-media.stanford.edu/assets/favicon/apple-touch-icon-${size}x${size}.png`, @@ -47,30 +42,20 @@ export const metadata = { // https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config export const revalidate = false; -const RootLayout = async ({children, modal}: { children: React.ReactNode, modal: React.ReactNode }) => { - const draftMode = isDraftMode(); - const siteSettingsConfig = await getConfigPage('StanfordBasicSiteSetting') +const RootLayout = ({children, modal}: { children: React.ReactNode, modal: React.ReactNode }) => { + const isPreview = isPreviewMode(); return ( - {draftMode && <>} - - {/* Add Google Analytics and SiteImprove when not in draft mode. */} - {(!draftMode && siteSettingsConfig?.suGoogleAnalytics) && - <> -