Skip to content

Commit

Permalink
Replace next Draft Mode with custom cookie and preview route
Browse files Browse the repository at this point in the history
  • Loading branch information
pookmish committed Mar 29, 2024
1 parent 74275ba commit 6c9be1b
Show file tree
Hide file tree
Showing 26 changed files with 400 additions and 408 deletions.
41 changes: 22 additions & 19 deletions .gitpod.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

additionalRepositories:
- url: https://github.com/SU-SWS/ace-stanfordlagunita/
- url: https://github.com/SU-SWS/acsf-cardinalsites-public
checkoutLocation: back
checkoutLocation: front
ports:
Expand Down Expand Up @@ -37,12 +37,14 @@ tasks:
mkdir -p blt &&
cp .gitpod/blt.yml blt/local.blt.yml &&
find docroot/sites/ -name 'local*' | xargs rm -rf &&
cp .gitpod/global.settings.php docroot/sites/settings/global.settings.php &&
cp .gitpod/default.local.services.yml docroot/sites/local.services.yml &&
export NEXT_PUBLIC_DRUPAL_BASE_URL=`gp url 8001` &&
export PREVIEW_URL=${NEXT_PUBLIC_DRUPAL_BASE_URL#"https://"} &&
blt blt:telemetry:disable --no-interaction &&
blt settings &&
blt drupal:install --site=supress -n &&
drush @supress.local cset system.theme default stanford_profile_admin_theme -y &&
blt drupal:install -n &&
drush cset system.theme default claro -y &&
cd /workspace/front &&
cp .env.example .env.local &&
sed -i 's/#DRUPAL_REVALIDATE_SECRET/DRUPAL_REVALIDATE_SECRET/' .env.local &&
Expand All @@ -57,37 +59,38 @@ tasks:
find docroot -name 'local.drush.yml' | xargs rm &&
export NEXT_PUBLIC_DRUPAL_BASE_URL=`gp url 8001` &&
export PREVIEW_URL=${NEXT_PUBLIC_DRUPAL_BASE_URL#"https://"} &&
echo "<?php \$sites['$PREVIEW_URL'] = 'supress';" > docroot/sites/local.sites.php &&
blt blt:telemetry:disable --no-interaction &&
echo 'Establishing Settings' &&
blt settings &&
echo 'Logging Into Drupal' &&
drush @supress.local uli --uri=$NEXT_PUBLIC_DRUPAL_BASE_URL &&
drush @supress.local uli --uri=$NEXT_PUBLIC_DRUPAL_BASE_URL | xargs gp preview --external &&
drush uli --uri=$NEXT_PUBLIC_DRUPAL_BASE_URL &&
drush uli --uri=$NEXT_PUBLIC_DRUPAL_BASE_URL | xargs gp preview --external &&
git config core.fileMode false &&
echo 'Connecting Drupal to Frontend' &&
drush @supress.local su-next-connect "$(gp url 3000)" --preview-secret=DRUPAL_PREVIEW_SECRET --revalidation-secret=DRUPAL_REVALIDATION_SECRET &&
drush su-next-connect "$(gp url 3000)" --preview-secret=DRUPAL_PREVIEW_SECRET --revalidation-secret=DRUPAL_REVALIDATION_SECRET &&
cd /workspace/front &&
yarn install &&
yarn config set --home enableTelemetry 0 &&
yarn next telemetry disable &&
sed -i -r "s|NEXT_PUBLIC_DRUPAL_BASE_URL.*|NEXT_PUBLIC_DRUPAL_BASE_URL=$NEXT_PUBLIC_DRUPAL_BASE_URL|g" .env.local &&
yarn dev &
gp ports await 3000 &&
gp url 3000 | xargs gp preview --external
- name: SSH Keys
command: |
rm -rf ~/.ssh/id_rsa &&
rm -rf ~/.ssh/id_rsa.pub &&
eval $(command gp env -e) &&
mkdir -p ~/.ssh &&
[[ ! -z $SSH_PUBLIC_KEY ]] &&
echo $SSH_PUBLIC_KEY | base64 -d > ~/.ssh/id_rsa.pub &&
chmod 644 ~/.ssh/id_rsa.pub &&
[[ ! -z $SSH_PRIVATE_KEY ]] &&
echo $SSH_PRIVATE_KEY | base64 -d > ~/.ssh/id_rsa &&
chmod 600 ~/.ssh/id_rsa &&
cd /workspace/front &&
[[ ! -z $GITCONFIG ]] &&
echo $GITCONFIG | base64 -d > ~/.gitconfig &&
chmod 644 ~/.gitconfig
git remote set-url origin $(echo $GITPOD_WORKSPACE_CONTEXT | jq -r .repository.cloneUrl | sed -E 's|^.*.com/(.*)$|git@github.com:\1|')
mkdir -p ~/.ssh
if [[ ! -z $SSH_PUBLIC_KEY ]]; then
echo $SSH_PUBLIC_KEY | base64 -d > ~/.ssh/id_rsa.pub && chmod 644 ~/.ssh/id_rsa.pub
fi
if [[ ! -z $SSH_PRIVATE_KEY ]]; then
echo $SSH_PRIVATE_KEY | base64 -d > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa
fi
if [[ ! -z $GITCONFIG ]]; then
echo $GITCONFIG | base64 -d > ~/.gitconfig && chmod 644 ~/.gitconfig
fi
vscode:
extensions:
Expand Down
32 changes: 3 additions & 29 deletions app/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,28 @@
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;
export const dynamic = 'force-static';

const Page = async ({params}: PageProps) => {
const path = getPathFromContext({params})
const inDraft = isDraftMode();

const {redirect: redirectPath, entity, error} = await getEntityFromPath<NodeUnion>(path, inDraft)
const {redirect: redirectPath, entity, error} = await getEntityFromPath<NodeUnion>(path)

if (error) throw new Error(error);
if (redirectPath?.url) redirect(redirectPath.url)
if (!entity) notFound();

return (
<>
<UnpublishedBanner status={entity.status}>
Unpublished Page
</UnpublishedBanner>
<NodePage node={entity}/>
</>
)
return <NodePage node={entity}/>
}

export const generateMetadata = async ({params}: PageProps): Promise<Metadata> => {
// 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<NodeUnion>(path)
return entity ? getNodeMetadata(entity) : {};
Expand All @@ -46,18 +34,4 @@ export const generateStaticParams = async (): Promise<PageProps["params"][]> =>
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<string, string | string[] | undefined>
}


export default Page;
9 changes: 0 additions & 9 deletions app/api/draft/disable/route.tsx

This file was deleted.

13 changes: 9 additions & 4 deletions app/api/draft/route.tsx
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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}`)
}
33 changes: 9 additions & 24 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -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`,
Expand Down Expand Up @@ -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>('StanfordBasicSiteSetting')
const RootLayout = ({children, modal}: { children: React.ReactNode, modal: React.ReactNode }) => {
const isPreview = isPreviewMode();
return (
<html lang="en" className={sourceSans3.className}>
{draftMode && <><Editori11y/><DrupalWindowSync/></>}

{/* Add Google Analytics and SiteImprove when not in draft mode. */}
{(!draftMode && siteSettingsConfig?.suGoogleAnalytics) &&
<>
<Script async src="//siteimproveanalytics.com/js/siteanalyze_80352.js"/>
<GoogleAnalytics gaId={siteSettingsConfig?.suGoogleAnalytics}/>
</>
{/* Add Google Analytics and SiteImprove when not in preview mode. */}
{!isPreview &&
<UserAnalytics/>
}
<DrupalWindowSync/>
<body>
<nav aria-label="Skip Links">
<a href="#main-content" className="skiplink">Skip to main content</a>
</nav>

{/* Automatically exit "Draft" mode upon the page loading. This prevents unwanted uncached data fetching. */}
{draftMode &&
<Link href="/api/draft/disable" tabIndex={-1} className="sr-only">Disable Draft Mode</Link>
}

<div className="flex flex-col min-h-screen">
<PageHeader/>
<main id="main-content" className="flex-grow mb-32">
Expand Down
4 changes: 2 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Rows from "@components/paragraphs/rows/rows";
import {notFound} from "next/navigation";
import {getEntityFromPath} from "@lib/gql/gql-queries";
import {NodeStanfordPage, NodeUnion} from "@lib/gql/__generated__/drupal.d";
import {isDraftMode} from "@lib/drupal/utils";
import {isPreviewMode} from "@lib/drupal/utils";
import {Metadata} from "next";
import {getNodeMetadata} from "./[...slug]/metadata";
import BannerParagraph from "@components/paragraphs/stanford-banner/banner-paragraph";
Expand All @@ -12,7 +12,7 @@ export const revalidate = false;
export const dynamic = 'force-static';

const Home = async () => {
const {entity, error} = await getEntityFromPath<NodeStanfordPage>('/', isDraftMode());
const {entity, error} = await getEntityFromPath<NodeStanfordPage>('/', isPreviewMode());

if (error) throw new Error(error);
if (!entity) notFound();
Expand Down
32 changes: 32 additions & 0 deletions app/preview/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import NodePage from "@components/nodes/pages/node-page";
import UnpublishedBanner from "@components/elements/unpublished-banner";
import {NodeUnion} from "@lib/gql/__generated__/drupal.d";
import {getEntityFromPath} from "@lib/gql/gql-queries";
import {notFound} from "next/navigation";
import Editori11y from "@components/tools/editorially";
import {getPathFromContext, isPreviewMode, PageProps} from "@lib/drupal/utils";

const Page = async ({params}: PageProps) => {
const path = getPathFromContext({params})
if (!isPreviewMode()) notFound();

const { entity, error} = await getEntityFromPath<NodeUnion>(path, true)

if (error) throw new Error(error);
if (!entity) notFound();

return (
<>
<Editori11y/>
<UnpublishedBanner status={false}>
Preview Mode
</UnpublishedBanner>
<UnpublishedBanner status={entity.status}>
Unpublished Page
</UnpublishedBanner>
<NodePage node={entity}/>
</>
)
}

export default Page;
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "press-nextjs",
"name": "decoupled-cardinalsites",
"version": "0.1.0",
"private": true,
"scripts": {
Expand All @@ -20,9 +20,9 @@
"@next/third-parties": "^14.1.4",
"@tailwindcss/container-queries": "^0.1.1",
"@types/node": "^20.11.30",
"@types/react": "^18.2.73",
"@types/react": "^18.2.72",
"@types/react-dom": "^18.2.22",
"algoliasearch": "^4.23.2",
"algoliasearch": "^4.23.1",
"autoprefixer": "^10.4.19",
"axios": "^1.6.8",
"clsx": "^2.1.0",
Expand All @@ -35,7 +35,7 @@
"graphql-tag": "^2.12.6",
"html-entities": "^2.5.2",
"html-react-parser": "^5.1.9",
"next": "^14.2.0-canary.45",
"next": "^14.2.0-canary.43",
"next-drupal": "^1.6.0",
"postcss": "^8.4.38",
"qs": "^6.12.0",
Expand All @@ -47,7 +47,7 @@
"react-tiny-oembed": "^1.1.0",
"sharp": "^0.33.3",
"tailwind-merge": "^2.2.2",
"tailwindcss": "^3.4.3",
"tailwindcss": "^3.4.1",
"typescript": "^5.4.3",
"usehooks-ts": "^3.0.2"
},
Expand Down
27 changes: 14 additions & 13 deletions src/components/elements/drupal-window-sync.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
"use client";

import {usePathname} from "next/navigation";
import {useEffect} from "react";
import {useIsClient} from "usehooks-ts";

const DrupalWindowSync = () => {
const pathname = usePathname();
if (!useIsClient()) return;

useEffect(() => {
if (!pathname) return;

if (
pathname &&
!pathname?.startsWith('/gallery/') &&
window &&
window.top !== window.self
) {
window.parent.postMessage({type: "NEXT_DRUPAL_ROUTE_SYNC", path: pathname}, process.env.NEXT_PUBLIC_DRUPAL_BASE_URL as string)
}
}, [pathname]);
if (
pathname &&
!pathname?.startsWith('/gallery/') &&
!pathname?.startsWith('/preview/') &&
window &&
window.top !== window.self
) {
window.parent.postMessage({
type: "NEXT_DRUPAL_ROUTE_SYNC",
path: pathname
}, process.env.NEXT_PUBLIC_DRUPAL_BASE_URL as string)
}
return null;
}

Expand Down
18 changes: 18 additions & 0 deletions src/components/elements/user-analytics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {getConfigPage} from "@lib/gql/gql-queries";
import {StanfordBasicSiteSetting} from "@lib/gql/__generated__/drupal";
import Script from "next/script";
import {GoogleAnalytics} from "@next/third-parties/google";
import {isPreviewMode} from "@lib/drupal/utils";

const UserAnalytics = async () => {
if (isPreviewMode()) return;
const siteSettingsConfig = await getConfigPage<StanfordBasicSiteSetting>('StanfordBasicSiteSetting')
if (!siteSettingsConfig?.suGoogleAnalytics) return;
return (
<>
<Script async src="//siteimproveanalytics.com/js/siteanalyze_80352.js"/>
<GoogleAnalytics gaId={siteSettingsConfig?.suGoogleAnalytics}/>
</>
)
}
export default UserAnalytics;
Loading

0 comments on commit 6c9be1b

Please sign in to comment.