Skip to content

Commit

Permalink
Refactor preview mode for drupal to use manual cookie instead of draf…
Browse files Browse the repository at this point in the history
…t mode
  • Loading branch information
pookmish committed Mar 29, 2024
1 parent 6aed489 commit 235af44
Show file tree
Hide file tree
Showing 30 changed files with 229 additions and 200 deletions.
5 changes: 0 additions & 5 deletions app/(public)/admin/paragraph/[id]/route.tsx

This file was deleted.

37 changes: 0 additions & 37 deletions app/(public)/layout.tsx

This file was deleted.

20 changes: 0 additions & 20 deletions app/(public)/not-found.tsx

This file was deleted.

File renamed without changes.
6 changes: 3 additions & 3 deletions app/(public)/[...slug]/page.tsx → app/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {getNodeMetadata} from "./metadata";
import LibraryHeader from "@/components/node/sul-library/library-header";
import InternalHeaderBanner from "@/components/patterns/internal-header-banner";
import SecondaryMenu from "@/components/menu/secondary-menu";
import {isDraftMode} from "@/lib/drupal/is-draft-mode";
import {isPreviewMode} from "@/lib/drupal/is-draft-mode";
import UnpublishedBanner from "@/components/patterns/unpublished-banner";
import {getAllNodePaths, getEntityFromPath, getMenu} from "@/lib/gql/fetcher";
import {NodeUnion} from "@/lib/gql/__generated__/drupal.d";
Expand All @@ -15,7 +15,7 @@ export const dynamic = 'force-static';

const NodePage = async ({params}: PageProps) => {
const path = getPathFromContext({params});
const {redirect: routeRedirect, entity} = await getEntityFromPath<NodeUnion>(path, isDraftMode());
const {redirect: routeRedirect, entity} = await getEntityFromPath<NodeUnion>(path);

if (routeRedirect) redirect(routeRedirect.url);
if (!entity) notFound();
Expand Down Expand Up @@ -83,7 +83,7 @@ const NodePage = async ({params}: PageProps) => {
}

export const generateMetadata = async ({params}: PageProps): Promise<Metadata> => {
if (isDraftMode()) return {};
if (isPreviewMode()) return {};
const path = getPathFromContext({params});
const {entity} = await getEntityFromPath<NodeUnion>(path);
return entity ? getNodeMetadata(entity) : {}
Expand Down
7 changes: 0 additions & 7 deletions app/api/draft/disable/route.tsx

This file was deleted.

37 changes: 25 additions & 12 deletions app/api/draft/route.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
// route handler with secret and slug
import {draftMode} from 'next/headers'
import {NextRequest, NextResponse} from "next/server";
import {redirect} from 'next/navigation'
import {cookies} from "next/headers";

export async function GET(request: Request) {
// Parse query string parameters
const {searchParams} = new URL(request.url)
const secret = searchParams.get('secret')
const slug = searchParams.get('slug')
export const revalidate = 0;

export async function GET(request: NextRequest) {

const secret = request.nextUrl.searchParams.get('secret')
const slug = request.nextUrl.searchParams.get('slug')

// Check the secret and next parameters
// This secret should only be known to this route handler and the CMS
if (secret !== process.env.DRUPAL_PREVIEW_SECRET || !slug) {
return new Response('Invalid token', {status: 401})
if (secret !== process.env.DRUPAL_PREVIEW_SECRET) {
return NextResponse.json({message: 'Invalid token'}, {status: 401})
}
// Enable Draft Mode by setting the cookie
draftMode().enable()
redirect(slug)

if (!slug) {
return NextResponse.json({message: 'Invalid slug path'}, {status: 401})
}
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(`/preview${slug}`)
}
File renamed without changes.
File renamed without changes.
34 changes: 28 additions & 6 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import "../src/styles/globals.css"

import Editori11y from "@/components/editori11y";
import {ReactNode} from "react";
import {Icon} from "next/dist/lib/metadata/types/metadata-types";
import {isDraftMode} from "@/lib/drupal/is-draft-mode";
import DrupalWindowSync from "@/components/utils/drupal-window-sync";
import Script from "next/script";
import {GoogleAnalytics} from "@next/third-parties/google";
import Header from "@/components/layout/header";
import LibraryFooter from "@/components/layout/library-footer";
import GlobalFooter from "@/components/layout/global-footer";
import {isPreviewMode} from "@/lib/drupal/is-draft-mode";

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`,
Expand Down Expand Up @@ -36,16 +41,33 @@ export const metadata = {
export const revalidate = false;

const RootLayout = ({children, modal}: { children: ReactNode, modal: ReactNode }) => {
const draftMode = isDraftMode();
const previewMode = isPreviewMode()
return (
<html lang="en">
{draftMode && <Editori11y/>}
<DrupalWindowSync/>
<body>
<nav aria-label="Skip link">
<a className="skiplink" href="#main-content">Skip to main content</a>
</nav>
{children}
{modal}
{(!previewMode && process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID) &&
<>
<Script async src="//siteimproveanalytics.com/js/siteanalyze_80352.js"/>
<GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID}/>
</>
}
<div className="grid grid-rows-1 min-h-screen">
<div>
<Header/>
{children}
{modal}
</div>

<footer className="row-start-2 row-end-3">
<LibraryFooter/>
<GlobalFooter/>
</footer>
</div>

</body>
</html>
)
Expand Down
36 changes: 11 additions & 25 deletions app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,20 @@
import InternalHeaderBanner from "@/components/patterns/internal-header-banner";
import Header from "@/components/layout/header";
import LibraryFooter from "@/components/layout/library-footer";
import GlobalFooter from "@/components/layout/global-footer";

const NotFound = () => {
return (
<div className="grid grid-rows-1 min-h-screen">
<div>
<Header/>
<main id="main-content" className="mb-50">
<InternalHeaderBanner>
<h1
className="w-full max-w-[calc(100vw-10rem)] md::max-w-[calc(100vw-20rem)] 3xl:max-w-[calc(1500px-20rem)] mx-auto relative text-white mt-80 md:mt-100 mb-50 p-0">
Page Not Found
</h1>
</InternalHeaderBanner>

<main id="main-content" className="mb-50">
<InternalHeaderBanner>
<h1
className="w-full max-w-[calc(100vw-10rem)] md::max-w-[calc(100vw-20rem)] 3xl:max-w-[calc(1500px-20rem)] mx-auto relative text-white mt-80 md:mt-100 mb-50 p-0">
Page Not Found
</h1>
</InternalHeaderBanner>

<div className="centered mb-50">
Unable to find the content you are looking for. Please try the <a href="/all">search</a> to find what you
were looking for.
</div>
</main>
<div className="centered mb-50">
Unable to find the content you are looking for. Please try the <a href="/all">search</a> to find what you were
looking for.
</div>

<footer className="row-start-2 row-end-3">
<LibraryFooter/>
<GlobalFooter/>
</footer>
</div>
</main>
)
}
export default NotFound;
File renamed without changes.
16 changes: 16 additions & 0 deletions app/preview/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {notFound} from "next/navigation";
import {isPreviewMode} from "@/lib/drupal/is-draft-mode";
import NodePage from "../../[...slug]/page";

const PreviewNodePage = async ({params}: PageProps) => {
if (!isPreviewMode()) notFound();
return <NodePage params={params}/>
}

type PageProps = {
params: { slug: string | string[] }
searchParams?: Record<string, string | string[] | undefined>
}


export default PreviewNodePage;
10 changes: 10 additions & 0 deletions app/preview/home/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {notFound} from "next/navigation";
import {isPreviewMode} from "@/lib/drupal/is-draft-mode";
import Page from "../../page";

const PreviewHomePage = async () => {
if (!isPreviewMode()) notFound();
return <Page/>
}

export default PreviewHomePage
22 changes: 22 additions & 0 deletions app/preview/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {ReactNode} from "react";
import {isPreviewMode} from "@/lib/drupal/is-draft-mode";
import Editori11y from "@/components/editori11y";
import {ExclamationCircleIcon} from "@heroicons/react/20/solid";

const RootLayout = ({children}: { children: ReactNode }) => {
const previewMode = isPreviewMode()
return (
<>
{previewMode &&
<>
<div className="bg-illuminating py-10 text-3xl font-bold">
<div className="centered-container flex gap-10"><ExclamationCircleIcon width={20}/>Previewing Content</div>
</div>
<Editori11y/>
</>
}
{children}
</>
)
}
export default RootLayout;
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@
"@mui/base": "^5.0.0-beta.41",
"@next/third-parties": "^14.1.4",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.11",
"@tailwindcss/typography": "^0.5.12",
"@tanstack/react-query": "^5.28.9",
"@types/node": "^20.11.30",
"@types/react": "^18.2.72",
"@types/react": "^18.2.73",
"autoprefixer": "^10.4.19",
"axios": "^1.6.8",
"critters": "^0.0.22",
"decanter": "^7.2.0",
"graphql": "^16.8.1",
"graphql-request": "^6.1.0",
"graphql-tag": "^2.12.6",
"html-react-parser": "^5.1.9",
"html-react-parser": "^5.1.10",
"jsona": "^1.12.1",
"next": "^14.1.4",
"next-drupal": "^1.6.0",
Expand All @@ -44,7 +44,7 @@
"react-tiny-oembed": "^1.1.0",
"sharp": "^0.33.3",
"tailwind-merge": "^2.2.2",
"tailwindcss": "^3.4.1",
"tailwindcss": "^3.4.3",
"typescript": "^5.4.3",
"usehooks-ts": "^3.0.2"
},
Expand Down
4 changes: 2 additions & 2 deletions src/components/layout/library-footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import TwitterIcon from "@/components/patterns/icons/TwitterIcon";
import InstagramIcon from "@/components/patterns/icons/InstagramIcon";
import YoutubeIcon from "@/components/patterns/icons/YoutubeIcon";
import {ReactNode} from "react";
import {isDraftMode} from "@/lib/drupal/is-draft-mode";
import {isPreviewMode} from "@/lib/drupal/is-draft-mode";

const LibraryFooter = () => {
return (
Expand Down Expand Up @@ -94,7 +94,7 @@ const _SocialLinks = () => {
<span className="sr-only">Youtube</span>
</FooterLink>

{isDraftMode() &&
{isPreviewMode() &&
<Link className="sr-only" href="/api/draft/disable" prefetch>
Disable Draft Mode
</Link>
Expand Down
5 changes: 2 additions & 3 deletions src/components/menu/main-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {useIsDesktop} from "@/lib/hooks/useIsDesktop";
import useActiveTrail from "@/lib/hooks/useActiveTrail";
import SearchForm from "@/components/search/search-form";
import SearchModal from "@/components/search/search-modal";
import useNavigationEvent from "@/lib/hooks/useNavigationEvent";
import useOutsideClick from "@/lib/hooks/useOutsideClick";
import {usePathname} from "next/navigation";
import {useBoolean} from "usehooks-ts";
Expand All @@ -17,7 +16,7 @@ const MainMenu = ({menuItems}: { menuItems: MenuItemType[] }) => {
const {value: menuOpen, setFalse: closeMenu, toggle: toggleMenu} = useBoolean(false);
const {value: addCloseAnimation, setValue: setAddCloseAnimation} = useBoolean(false);

const browserUrl = useNavigationEvent();
const browserUrl = usePathname();
const activeTrail = useActiveTrail(menuItems, usePathname() || '');
const isDesktop = useIsDesktop();

Expand Down Expand Up @@ -115,7 +114,7 @@ type MenuItemProps = MenuItemType & {
}

const MenuItem = ({id, title, url, children, expanded, tabIndex = 0, activeTrail = [], menuLevel = 0}: MenuItemProps) => {
const browserUrl = useNavigationEvent();
const browserUrl = usePathname();
const {value: submenuOpen, setFalse: closeSubmenu, toggle: toggleSubmenu} = useBoolean(false);
const active = activeTrail.includes(id);

Expand Down
4 changes: 2 additions & 2 deletions src/components/menu/secondary-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import Link from "@/components/patterns/elements/drupal-link";
import {ChevronDownIcon} from "@heroicons/react/20/solid";
import useActiveTrail from "@/lib/hooks/useActiveTrail";
import {useIsDesktop} from "@/lib/hooks/useIsDesktop";
import useNavigationEvent from "@/lib/hooks/useNavigationEvent";
import useOutsideClick from "@/lib/hooks/useOutsideClick";
import {useBoolean} from "usehooks-ts";
import {MenuItem} from "@/lib/gql/__generated__/drupal.d";
import {usePathname} from "next/navigation";

const getCurrentPageTitle = (activeTrail: string[], items: MenuItem[], trail: string[]): string | undefined => {
const currentItem = items.find(item => item.id === trail.at(0));
Expand All @@ -23,7 +23,7 @@ const getCurrentPageTitle = (activeTrail: string[], items: MenuItem[], trail: st
}

const SecondaryMenu = ({menuItems, currentPath}: { menuItems: MenuItem[], currentPath: string }) => {
const browserUrl = useNavigationEvent();
const browserUrl = usePathname();
const {value: menuOpen, setFalse: closeMenu, toggle: toggleMenuOpen} = useBoolean(false)
const outsideClickProps = useOutsideClick(closeMenu)
const activeTrail = useActiveTrail(menuItems, currentPath);
Expand Down
Loading

0 comments on commit 235af44

Please sign in to comment.