From 517c3878b8f77ccd6f93a960e79322512ea8aa55 Mon Sep 17 00:00:00 2001 From: JohnAlbin Date: Tue, 27 Feb 2024 00:31:50 +0800 Subject: [PATCH 1/7] test(next-drupal): fix bug in jsonRpc test util and rename utils files --- packages/next-drupal/tests/utils/mocks/index.ts | 4 ++-- .../tests/utils/mocks/{logger.ts => mockLogger.ts} | 0 .../tests/utils/mocks/{fetch.ts => spyOnFetch.ts} | 0 packages/next-drupal/tests/utils/rpc.ts | 6 +++--- 4 files changed, 5 insertions(+), 5 deletions(-) rename packages/next-drupal/tests/utils/mocks/{logger.ts => mockLogger.ts} (100%) rename packages/next-drupal/tests/utils/mocks/{fetch.ts => spyOnFetch.ts} (100%) diff --git a/packages/next-drupal/tests/utils/mocks/index.ts b/packages/next-drupal/tests/utils/mocks/index.ts index 96ddd33a..970d69af 100644 --- a/packages/next-drupal/tests/utils/mocks/index.ts +++ b/packages/next-drupal/tests/utils/mocks/index.ts @@ -1,3 +1,3 @@ export * from "./data" -export * from "./fetch" -export * from "./logger" +export * from "./spyOnFetch" +export * from "./mockLogger" diff --git a/packages/next-drupal/tests/utils/mocks/logger.ts b/packages/next-drupal/tests/utils/mocks/mockLogger.ts similarity index 100% rename from packages/next-drupal/tests/utils/mocks/logger.ts rename to packages/next-drupal/tests/utils/mocks/mockLogger.ts diff --git a/packages/next-drupal/tests/utils/mocks/fetch.ts b/packages/next-drupal/tests/utils/mocks/spyOnFetch.ts similarity index 100% rename from packages/next-drupal/tests/utils/mocks/fetch.ts rename to packages/next-drupal/tests/utils/mocks/spyOnFetch.ts diff --git a/packages/next-drupal/tests/utils/rpc.ts b/packages/next-drupal/tests/utils/rpc.ts index 7b3e4b7d..86533e8c 100644 --- a/packages/next-drupal/tests/utils/rpc.ts +++ b/packages/next-drupal/tests/utils/rpc.ts @@ -8,10 +8,10 @@ class JsonRpc extends NextDrupalBase { this.apiPrefix = "/jsonrpc" } - async execute(body) { - const endpoint = await jsonRpc.buildEndpoint() + async execute(body: object) { + const endpoint = await this.buildEndpoint() - const response = await jsonRpc.fetch(endpoint, { + const response = await this.fetch(endpoint, { method: "POST", body: JSON.stringify(body), withAuth: true, From 21b6c74d71695d17ad9021ede0ae6a516945aa72 Mon Sep 17 00:00:00 2001 From: JohnAlbin Date: Tue, 23 Apr 2024 00:27:04 +0800 Subject: [PATCH 2/7] style(next): fix PHP code style in EntityActionEventDispatcher --- .../src/EventSubscriber/EntityActionEventDispatcher.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/next/src/EventSubscriber/EntityActionEventDispatcher.php b/modules/next/src/EventSubscriber/EntityActionEventDispatcher.php index ee043a66..a97233c4 100644 --- a/modules/next/src/EventSubscriber/EntityActionEventDispatcher.php +++ b/modules/next/src/EventSubscriber/EntityActionEventDispatcher.php @@ -13,7 +13,7 @@ final class EntityActionEventDispatcher implements DestructableInterface { /** - * The events to dispach. + * The events to dispatch. * * @var \Drupal\next\Event\EntityActionEvent[] */ @@ -23,8 +23,8 @@ final class EntityActionEventDispatcher implements DestructableInterface { * EntityActionEventDispatcher constructor. */ public function __construct( - private EventDispatcherInterface $eventDispatcher - ) { + private EventDispatcherInterface $eventDispatcher, + ) { } /** From 4285e43fb15e901f846a9f623bef02e5e6bc44cd Mon Sep 17 00:00:00 2001 From: JohnAlbin Date: Mon, 22 Apr 2024 22:52:38 +0800 Subject: [PATCH 3/7] feat(basic-starter): upgrade starters to NextDrupal classes Issue #601 --- .../example-router-migration/lib/drupal.ts | 6 +- starters/basic-starter/.gitignore | 3 +- starters/basic-starter/README.md | 2 +- starters/basic-starter/components/Layout.tsx | 20 +---- .../components/drupal/ArticleTeaser.tsx | 2 +- .../components/{ => misc}/PreviewAlert.tsx | 7 +- .../components/navigation/HeaderNav.tsx | 21 +++++ .../components/navigation/Link.tsx | 23 +++++ starters/basic-starter/lib/drupal.ts | 12 +-- starters/basic-starter/tsconfig.json | 7 +- starters/graphql-starter/.gitignore | 3 +- starters/graphql-starter/README.md | 4 + .../graphql-starter/components/Layout.tsx | 20 +---- .../components/drupal/Article.tsx | 11 ++- .../components/drupal/ArticleTeaser.tsx | 13 ++- .../components/drupal/BasicPage.tsx | 4 +- .../components/{ => misc}/PreviewAlert.tsx | 7 +- .../components/navigation/HeaderNav.tsx | 21 +++++ .../components/navigation/Link.tsx | 23 +++++ starters/graphql-starter/lib/drupal.ts | 50 ++--------- .../lib/next-drupal-graphql.ts | 66 ++++++++++++++ starters/graphql-starter/pages/[...slug].tsx | 86 ++++++++++--------- starters/graphql-starter/pages/index.tsx | 16 ++-- starters/graphql-starter/tsconfig.json | 7 +- starters/graphql-starter/types/index.d.ts | 10 ++- 25 files changed, 281 insertions(+), 163 deletions(-) rename starters/basic-starter/components/{ => misc}/PreviewAlert.tsx (85%) create mode 100644 starters/basic-starter/components/navigation/HeaderNav.tsx create mode 100644 starters/basic-starter/components/navigation/Link.tsx rename starters/graphql-starter/components/{ => misc}/PreviewAlert.tsx (85%) create mode 100644 starters/graphql-starter/components/navigation/HeaderNav.tsx create mode 100644 starters/graphql-starter/components/navigation/Link.tsx create mode 100644 starters/graphql-starter/lib/next-drupal-graphql.ts diff --git a/examples/example-router-migration/lib/drupal.ts b/examples/example-router-migration/lib/drupal.ts index 1b13292e..509ee204 100644 --- a/examples/example-router-migration/lib/drupal.ts +++ b/examples/example-router-migration/lib/drupal.ts @@ -3,9 +3,9 @@ import { // NextDrupal } from "next-drupal" -const baseUrl: string = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL || "" -const clientId = process.env.DRUPAL_CLIENT_ID || "" -const clientSecret = process.env.DRUPAL_CLIENT_SECRET || "" +const baseUrl = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL as string +const clientId = process.env.DRUPAL_CLIENT_ID as string +const clientSecret = process.env.DRUPAL_CLIENT_SECRET as string export const drupal = new DrupalClient(baseUrl, { auth: { diff --git a/starters/basic-starter/.gitignore b/starters/basic-starter/.gitignore index 83ec79b6..081b7c17 100644 --- a/starters/basic-starter/.gitignore +++ b/starters/basic-starter/.gitignore @@ -6,8 +6,7 @@ .pnp.js .yarn/install-state.gz -# build/test artifacts -/.turbo +# testing /coverage # next.js diff --git a/starters/basic-starter/README.md b/starters/basic-starter/README.md index 03dfcfb4..0075ec73 100644 --- a/starters/basic-starter/README.md +++ b/starters/basic-starter/README.md @@ -1,6 +1,6 @@ # Basic Starter -A simple starter for building your site with Next.js and Drupal. +A simple starter for building your site with Next.js' Pages Router and Drupal. ## How to use diff --git a/starters/basic-starter/components/Layout.tsx b/starters/basic-starter/components/Layout.tsx index 672d0b48..cb1f4012 100644 --- a/starters/basic-starter/components/Layout.tsx +++ b/starters/basic-starter/components/Layout.tsx @@ -1,5 +1,5 @@ -import Link from "next/link" -import { PreviewAlert } from "@/components/PreviewAlert" +import { HeaderNav } from "@/components/navigation/HeaderNav" +import { PreviewAlert } from "@/components/misc/PreviewAlert" import type { ReactNode } from "react" export function Layout({ children }: { children: ReactNode }) { @@ -7,21 +7,7 @@ export function Layout({ children }: { children: ReactNode }) { <>
-
-
- - Next.js for Drupal - - - Read the docs - -
-
+
{children}
diff --git a/starters/basic-starter/components/drupal/ArticleTeaser.tsx b/starters/basic-starter/components/drupal/ArticleTeaser.tsx index 4909b4a1..8efeac62 100644 --- a/starters/basic-starter/components/drupal/ArticleTeaser.tsx +++ b/starters/basic-starter/components/drupal/ArticleTeaser.tsx @@ -1,5 +1,5 @@ import Image from "next/image" -import Link from "next/link" +import { Link } from "@/components/navigation/Link" import { absoluteUrl, formatDate } from "@/lib/utils" import type { DrupalNode } from "next-drupal" diff --git a/starters/basic-starter/components/PreviewAlert.tsx b/starters/basic-starter/components/misc/PreviewAlert.tsx similarity index 85% rename from starters/basic-starter/components/PreviewAlert.tsx rename to starters/basic-starter/components/misc/PreviewAlert.tsx index abca6a68..edce5720 100644 --- a/starters/basic-starter/components/PreviewAlert.tsx +++ b/starters/basic-starter/components/misc/PreviewAlert.tsx @@ -14,13 +14,18 @@ export function PreviewAlert() { return null } + function buttonHandler() { + void fetch("/api/exit-preview") + setShowPreviewAlert(false) + } + return (

This page is a preview.{" "} diff --git a/starters/basic-starter/components/navigation/HeaderNav.tsx b/starters/basic-starter/components/navigation/HeaderNav.tsx new file mode 100644 index 00000000..bccb4e4c --- /dev/null +++ b/starters/basic-starter/components/navigation/HeaderNav.tsx @@ -0,0 +1,21 @@ +import { Link } from "@/components/navigation/Link" + +export function HeaderNav() { + return ( +

+
+ + Next.js for Drupal + + + Read the docs + +
+
+ ) +} diff --git a/starters/basic-starter/components/navigation/Link.tsx b/starters/basic-starter/components/navigation/Link.tsx new file mode 100644 index 00000000..23dbc4c0 --- /dev/null +++ b/starters/basic-starter/components/navigation/Link.tsx @@ -0,0 +1,23 @@ +import { forwardRef } from "react" +import NextLink from "next/link" +import type { AnchorHTMLAttributes, ReactNode } from "react" +import type { LinkProps as NextLinkProps } from "next/link" + +type LinkProps = NextLinkProps & + Omit, keyof NextLinkProps> & { + children?: ReactNode + } + +export const Link = forwardRef( + function LinkWithRef( + { + // Turn next/link prefetching off by default. + // @see https://github.com/vercel/next.js/discussions/24009 + prefetch = false, + ...rest + }, + ref + ) { + return + } +) diff --git a/starters/basic-starter/lib/drupal.ts b/starters/basic-starter/lib/drupal.ts index 513b3bf2..9b2f40c0 100644 --- a/starters/basic-starter/lib/drupal.ts +++ b/starters/basic-starter/lib/drupal.ts @@ -1,12 +1,14 @@ -import { DrupalClient } from "next-drupal" +import { NextDrupalPages } from "next-drupal" -const baseUrl: string = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL || "" -const clientId = process.env.DRUPAL_CLIENT_ID || "" -const clientSecret = process.env.DRUPAL_CLIENT_SECRET || "" +const baseUrl = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL as string +const clientId = process.env.DRUPAL_CLIENT_ID as string +const clientSecret = process.env.DRUPAL_CLIENT_SECRET as string -export const drupal = new DrupalClient(baseUrl, { +export const drupal = new NextDrupalPages(baseUrl, { auth: { clientId, clientSecret, }, + useDefaultEndpoints: true, + // debug: true, }) diff --git a/starters/basic-starter/tsconfig.json b/starters/basic-starter/tsconfig.json index ad54f56d..23ba4fd5 100644 --- a/starters/basic-starter/tsconfig.json +++ b/starters/basic-starter/tsconfig.json @@ -14,10 +14,15 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, + "plugins": [ + { + "name": "next" + } + ], "paths": { "@/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } diff --git a/starters/graphql-starter/.gitignore b/starters/graphql-starter/.gitignore index 83ec79b6..081b7c17 100644 --- a/starters/graphql-starter/.gitignore +++ b/starters/graphql-starter/.gitignore @@ -6,8 +6,7 @@ .pnp.js .yarn/install-state.gz -# build/test artifacts -/.turbo +# testing /coverage # next.js diff --git a/starters/graphql-starter/README.md b/starters/graphql-starter/README.md index 37621e7a..1e754465 100644 --- a/starters/graphql-starter/README.md +++ b/starters/graphql-starter/README.md @@ -6,6 +6,10 @@ A next-drupal starter for building your site with Next.js and GraphQL. `npx create-next-app -e https://github.com/chapter-three/next-drupal-graphql-starter` +## Deploy to Vercel + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fchapter-three%2Fnext-drupal-graphql-starter&env=NEXT_PUBLIC_DRUPAL_BASE_URL,NEXT_IMAGE_DOMAIN,DRUPAL_CLIENT_ID,DRUPAL_CLIENT_SECRET&envDescription=Learn%20more%20about%20environment%20variables&envLink=https%3A%2F%2Fnext-drupal.org%2Fdocs%2Fenvironment-variables&project-name=next-drupal&demo-title=Next.js%20for%20Drupal&demo-description=A%20next-generation%20front-end%20for%20your%20Drupal%20site.&demo-url=https%3A%2F%2Fdemo.next-drupal.org&demo-image=https%3A%2F%2Fnext-drupal.org%2Fimages%2Fdemo-screenshot.jpg) + ## Documentation See https://next-drupal.org diff --git a/starters/graphql-starter/components/Layout.tsx b/starters/graphql-starter/components/Layout.tsx index 672d0b48..cb1f4012 100644 --- a/starters/graphql-starter/components/Layout.tsx +++ b/starters/graphql-starter/components/Layout.tsx @@ -1,5 +1,5 @@ -import Link from "next/link" -import { PreviewAlert } from "@/components/PreviewAlert" +import { HeaderNav } from "@/components/navigation/HeaderNav" +import { PreviewAlert } from "@/components/misc/PreviewAlert" import type { ReactNode } from "react" export function Layout({ children }: { children: ReactNode }) { @@ -7,21 +7,7 @@ export function Layout({ children }: { children: ReactNode }) { <>
-
-
- - Next.js for Drupal - - - Read the docs - -
-
+
{children}
diff --git a/starters/graphql-starter/components/drupal/Article.tsx b/starters/graphql-starter/components/drupal/Article.tsx index 2e9a05a3..e761ec1b 100644 --- a/starters/graphql-starter/components/drupal/Article.tsx +++ b/starters/graphql-starter/components/drupal/Article.tsx @@ -1,9 +1,9 @@ import Image from "next/image" import { formatDate } from "@/lib/utils" -import type { NodeArticle } from "@/types" +import type { DrupalArticle } from "@/types" interface ArticleProps { - node: NodeArticle + node: DrupalArticle } export function Article({ node, ...props }: ArticleProps) { @@ -11,13 +11,12 @@ export function Article({ node, ...props }: ArticleProps) {

{node.title}

- {node.author?.displayName ? ( + {node.author?.name ? ( - Posted by{" "} - {node.author.displayName} + Posted by {node.author.name} ) : null} - - {formatDate(node.created)} + - {formatDate(node.created.time)}
{node.image && (
diff --git a/starters/graphql-starter/components/drupal/ArticleTeaser.tsx b/starters/graphql-starter/components/drupal/ArticleTeaser.tsx index ed88aca0..fd200f64 100644 --- a/starters/graphql-starter/components/drupal/ArticleTeaser.tsx +++ b/starters/graphql-starter/components/drupal/ArticleTeaser.tsx @@ -1,10 +1,10 @@ import Image from "next/image" -import Link from "next/link" +import { Link } from "@/components/navigation/Link" import { formatDate } from "@/lib/utils" -import type { NodeArticle } from "@/types" +import type { DrupalArticle } from "@/types" interface ArticleTeaserProps { - node: Partial + node: Partial } export function ArticleTeaser({ node, ...props }: ArticleTeaserProps) { @@ -14,13 +14,12 @@ export function ArticleTeaser({ node, ...props }: ArticleTeaserProps) {

{node.title}

- {node.author?.displayName ? ( + {node.author?.name ? ( - Posted by{" "} - {node.author.displayName} + Posted by {node.author.name} ) : null} - {node.created && - {formatDate(node.created)}} + {node.created && - {formatDate(node.created.time)}}
{node.image && (
diff --git a/starters/graphql-starter/components/drupal/BasicPage.tsx b/starters/graphql-starter/components/drupal/BasicPage.tsx index b4bc2ed6..a310a15a 100644 --- a/starters/graphql-starter/components/drupal/BasicPage.tsx +++ b/starters/graphql-starter/components/drupal/BasicPage.tsx @@ -1,7 +1,7 @@ -import type { NodePage } from "@/types" +import type { DrupalPage } from "@/types" interface BasicPageProps { - node: NodePage + node: DrupalPage } export function BasicPage({ node, ...props }: BasicPageProps) { diff --git a/starters/graphql-starter/components/PreviewAlert.tsx b/starters/graphql-starter/components/misc/PreviewAlert.tsx similarity index 85% rename from starters/graphql-starter/components/PreviewAlert.tsx rename to starters/graphql-starter/components/misc/PreviewAlert.tsx index abca6a68..edce5720 100644 --- a/starters/graphql-starter/components/PreviewAlert.tsx +++ b/starters/graphql-starter/components/misc/PreviewAlert.tsx @@ -14,13 +14,18 @@ export function PreviewAlert() { return null } + function buttonHandler() { + void fetch("/api/exit-preview") + setShowPreviewAlert(false) + } + return (

This page is a preview.{" "} diff --git a/starters/graphql-starter/components/navigation/HeaderNav.tsx b/starters/graphql-starter/components/navigation/HeaderNav.tsx new file mode 100644 index 00000000..bccb4e4c --- /dev/null +++ b/starters/graphql-starter/components/navigation/HeaderNav.tsx @@ -0,0 +1,21 @@ +import { Link } from "@/components/navigation/Link" + +export function HeaderNav() { + return ( +

+
+ + Next.js for Drupal + + + Read the docs + +
+
+ ) +} diff --git a/starters/graphql-starter/components/navigation/Link.tsx b/starters/graphql-starter/components/navigation/Link.tsx new file mode 100644 index 00000000..23dbc4c0 --- /dev/null +++ b/starters/graphql-starter/components/navigation/Link.tsx @@ -0,0 +1,23 @@ +import { forwardRef } from "react" +import NextLink from "next/link" +import type { AnchorHTMLAttributes, ReactNode } from "react" +import type { LinkProps as NextLinkProps } from "next/link" + +type LinkProps = NextLinkProps & + Omit, keyof NextLinkProps> & { + children?: ReactNode + } + +export const Link = forwardRef( + function LinkWithRef( + { + // Turn next/link prefetching off by default. + // @see https://github.com/vercel/next.js/discussions/24009 + prefetch = false, + ...rest + }, + ref + ) { + return + } +) diff --git a/starters/graphql-starter/lib/drupal.ts b/starters/graphql-starter/lib/drupal.ts index 7a7921c2..96c77a55 100644 --- a/starters/graphql-starter/lib/drupal.ts +++ b/starters/graphql-starter/lib/drupal.ts @@ -1,51 +1,13 @@ -import { DrupalClient } from "next-drupal" +import { NextDrupalGraphQL } from "./next-drupal-graphql" -const baseUrl: string = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL || "" -const clientId = process.env.DRUPAL_CLIENT_ID || "" -const clientSecret = process.env.DRUPAL_CLIENT_SECRET || "" +const baseUrl = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL as string +const clientId = process.env.DRUPAL_CLIENT_ID as string +const clientSecret = process.env.DRUPAL_CLIENT_SECRET as string -export const drupal = new DrupalClient(baseUrl, { +export const drupal = new NextDrupalGraphQL(baseUrl, { auth: { clientId, clientSecret, }, + // debug: true, }) - -export const graphqlEndpoint = drupal.buildUrl("/graphql") - -type QueryPayload = { - query: string - variables?: Record -} - -type QueryJsonResponse = { - data?: DataType - errors?: { message: string }[] -} - -// This is a wrapper around drupal.fetch. -// Acts as a query helper. -export async function query(payload: QueryPayload) { - const response = await drupal.fetch(graphqlEndpoint.toString(), { - method: "POST", - body: JSON.stringify(payload), - withAuth: true, // Make authenticated requests using OAuth. - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - }) - - if (!response?.ok) { - throw new Error(response.statusText) - } - - const { data, errors }: QueryJsonResponse = await response.json() - - if (errors) { - console.log(errors) - throw new Error(errors?.map((e) => e.message).join("\n") ?? "unknown") - } - - return data -} diff --git a/starters/graphql-starter/lib/next-drupal-graphql.ts b/starters/graphql-starter/lib/next-drupal-graphql.ts new file mode 100644 index 00000000..b6f5d80a --- /dev/null +++ b/starters/graphql-starter/lib/next-drupal-graphql.ts @@ -0,0 +1,66 @@ +// This is an example GraphQL implementation using NextDrupalBase, a lower-level +// class that contains helper methods and no JSON:API methods. + +import { NextDrupalBase } from "next-drupal" +import type { + BaseUrl, + EndpointSearchParams, + NextDrupalBaseOptions, +} from "next-drupal" + +const DEFAULT_API_PREFIX = "/graphql" + +export class NextDrupalGraphQL extends NextDrupalBase { + endpoint: string + + constructor(baseUrl: BaseUrl, options: NextDrupalBaseOptions = {}) { + super(baseUrl, options) + + const { apiPrefix = DEFAULT_API_PREFIX } = options + + this.apiPrefix = apiPrefix + + this.endpoint = this.buildUrl(this.apiPrefix).toString() + } + + async query(payload: QueryPayload) { + const response = await this.fetch(this.endpoint, { + method: "POST", + body: JSON.stringify(payload), + withAuth: true, // Make authenticated requests using OAuth. + }) + + if (!response?.ok) { + throw new Error(response.statusText) + } + + const { data, errors }: QueryJsonResponse = await response.json() + + if (errors) { + this.logger.log(errors) + throw new Error(errors?.map((e) => e.message).join("\n") ?? "unknown") + } + + return data + } + + // Since the endpoint doesn't change (even with different locales), there's + // no need to use this method; use NextDrupalGraphQL.query() directly. + async buildEndpoint({ + searchParams, + }: { + searchParams?: EndpointSearchParams + } = {}): Promise { + return this.buildUrl(this.apiPrefix, searchParams).toString() + } +} + +type QueryPayload = { + query: string + variables?: Record +} + +type QueryJsonResponse = { + data?: DataType + errors?: { message: string }[] +} diff --git a/starters/graphql-starter/pages/[...slug].tsx b/starters/graphql-starter/pages/[...slug].tsx index 80a20f16..4ca04106 100644 --- a/starters/graphql-starter/pages/[...slug].tsx +++ b/starters/graphql-starter/pages/[...slug].tsx @@ -2,18 +2,18 @@ import Head from "next/head" import { Article } from "@/components/drupal/Article" import { BasicPage } from "@/components/drupal/BasicPage" import { Layout } from "@/components/Layout" -import { drupal, query } from "@/lib/drupal" +import { drupal } from "@/lib/drupal" import type { GetStaticPaths, GetStaticProps, InferGetStaticPropsType, } from "next" -import type { NodeArticle, NodePage, NodesPath } from "@/types" +import type { DrupalArticle, DrupalPage, NodesPath } from "@/types" export const getStaticPaths = (async (context) => { // Fetch the paths for the first 50 articles and pages. // We'll fall back to on-demand generation for the rest. - const data = await query<{ + const data = await drupal.query<{ nodeArticles: NodesPath nodePages: NodesPath }>({ @@ -32,12 +32,10 @@ export const getStaticPaths = (async (context) => { }) // Build static paths. - const paths = drupal.buildStaticPathsParamsFromPaths( - [ - ...(data?.nodeArticles?.nodes as []), - ...(data?.nodePages?.nodes as []), - ].map(({ path }) => path) - ) + const paths = [ + ...(data?.nodeArticles?.nodes as { path: string }[]), + ...(data?.nodePages?.nodes as { path: string }[]), + ].map(({ path }) => ({ params: { slug: path.split("/").filter(Boolean) } })) return { paths, @@ -52,47 +50,53 @@ export const getStaticProps = (async (context) => { } } - const data = await query<{ - nodeByPath: NodeArticle | NodePage + const data = await drupal.query<{ + route: { entity: DrupalArticle | DrupalPage } }>({ query: `query ($path: String!){ - nodeByPath(path: $path) { - ... on NodeArticle { - __typename - id - title - path - author { - displayName - } - body { - processed - } - status - created - image { - width - url - height - } - } - ... on NodePage { - __typename - id - title - path - body { - processed + route(path: $path) { + ... on RouteInternal { + entity { + ... on NodeArticle { + __typename + id + title + path + author { + name + } + body { + processed + } + status + created { + time + } + image { + width + url + height + } + } + ... on NodePage { + __typename + id + title + path + body { + processed + } + } } } } }`, variables: { - path: `/${context.params.slug.join("/")}`, + path: `/${(context.params.slug as []).join("/")}`, }, }) - const resource = data?.nodeByPath + const resource = data?.route?.entity // If we're not in preview mode and the resource is not published, // Return page not found. @@ -108,7 +112,7 @@ export const getStaticProps = (async (context) => { }, } }) satisfies GetStaticProps<{ - resource: NodeArticle | NodePage + resource: DrupalArticle | DrupalPage }> export default function Page({ diff --git a/starters/graphql-starter/pages/index.tsx b/starters/graphql-starter/pages/index.tsx index c5c5bca6..14544955 100644 --- a/starters/graphql-starter/pages/index.tsx +++ b/starters/graphql-starter/pages/index.tsx @@ -1,15 +1,15 @@ import Head from "next/head" import { ArticleTeaser } from "@/components/drupal/ArticleTeaser" import { Layout } from "@/components/Layout" -import { query } from "@/lib/drupal" +import { drupal } from "@/lib/drupal" import type { InferGetStaticPropsType, GetStaticProps } from "next" -import type { NodeArticle } from "@/types" +import type { DrupalArticle } from "@/types" export const getStaticProps = (async (context) => { // Fetch the first 10 articles. - const data = await query<{ + const data = await drupal.query<{ nodeArticles: { - nodes: NodeArticle[] + nodes: DrupalArticle[] } }>({ query: ` @@ -20,12 +20,14 @@ export const getStaticProps = (async (context) => { title path author { - displayName + name } body { processed } - created + created { + time + } image { width url @@ -43,7 +45,7 @@ export const getStaticProps = (async (context) => { }, } }) satisfies GetStaticProps<{ - nodes: NodeArticle[] + nodes: DrupalArticle[] }> export default function Home({ diff --git a/starters/graphql-starter/tsconfig.json b/starters/graphql-starter/tsconfig.json index ad54f56d..23ba4fd5 100644 --- a/starters/graphql-starter/tsconfig.json +++ b/starters/graphql-starter/tsconfig.json @@ -14,10 +14,15 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, + "plugins": [ + { + "name": "next" + } + ], "paths": { "@/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } diff --git a/starters/graphql-starter/types/index.d.ts b/starters/graphql-starter/types/index.d.ts index fc52cc6e..95439dc8 100644 --- a/starters/graphql-starter/types/index.d.ts +++ b/starters/graphql-starter/types/index.d.ts @@ -15,10 +15,10 @@ export type Image = { } export type Author = { - displayName: string + name: string } -export type NodePage = { +export type DrupalPage = { __typename: "NodePage" id: string status: boolean @@ -29,7 +29,7 @@ export type NodePage = { } } -export type NodeArticle = { +export type DrupalArticle = { __typename: "NodeArticle" id: string status: boolean @@ -39,6 +39,8 @@ export type NodeArticle = { body: { processed: string } - created: string + created: { + time: string + } image: Image } From 199c7f927f8822cf8764a9ac98128530f7b860f5 Mon Sep 17 00:00:00 2001 From: JohnAlbin Date: Mon, 22 Apr 2024 22:53:07 +0800 Subject: [PATCH 4/7] feat(basic-starter): add Draft Mode to basic-starter and graphql-starter Issue #601 --- starters/basic-starter/pages/api/exit-preview.ts | 7 +++---- starters/basic-starter/pages/api/preview.ts | 5 +++-- starters/graphql-starter/pages/api/exit-preview.ts | 7 +++---- starters/graphql-starter/pages/api/preview.ts | 5 +++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/starters/basic-starter/pages/api/exit-preview.ts b/starters/basic-starter/pages/api/exit-preview.ts index a8cf12e8..f8847b39 100644 --- a/starters/basic-starter/pages/api/exit-preview.ts +++ b/starters/basic-starter/pages/api/exit-preview.ts @@ -1,10 +1,9 @@ +import { drupal } from "@/lib/drupal" import type { NextApiRequest, NextApiResponse } from "next" export default async function exit( - _: NextApiRequest, + request: NextApiRequest, response: NextApiResponse ) { - response.clearPreviewData() - response.writeHead(307, { Location: "/" }) - response.end() + await drupal.previewDisable(request, response) } diff --git a/starters/basic-starter/pages/api/preview.ts b/starters/basic-starter/pages/api/preview.ts index 7660eb64..a0733440 100644 --- a/starters/basic-starter/pages/api/preview.ts +++ b/starters/basic-starter/pages/api/preview.ts @@ -1,9 +1,10 @@ import { drupal } from "@/lib/drupal" import type { NextApiRequest, NextApiResponse } from "next" -export default async function handler( +export default async function draft( request: NextApiRequest, response: NextApiResponse ) { - await drupal.preview(request, response) + // Enables Preview mode and Draft mode. + await drupal.preview(request, response, { enable: true }) } diff --git a/starters/graphql-starter/pages/api/exit-preview.ts b/starters/graphql-starter/pages/api/exit-preview.ts index a8cf12e8..f8847b39 100644 --- a/starters/graphql-starter/pages/api/exit-preview.ts +++ b/starters/graphql-starter/pages/api/exit-preview.ts @@ -1,10 +1,9 @@ +import { drupal } from "@/lib/drupal" import type { NextApiRequest, NextApiResponse } from "next" export default async function exit( - _: NextApiRequest, + request: NextApiRequest, response: NextApiResponse ) { - response.clearPreviewData() - response.writeHead(307, { Location: "/" }) - response.end() + await drupal.previewDisable(request, response) } diff --git a/starters/graphql-starter/pages/api/preview.ts b/starters/graphql-starter/pages/api/preview.ts index 7660eb64..a0733440 100644 --- a/starters/graphql-starter/pages/api/preview.ts +++ b/starters/graphql-starter/pages/api/preview.ts @@ -1,9 +1,10 @@ import { drupal } from "@/lib/drupal" import type { NextApiRequest, NextApiResponse } from "next" -export default async function handler( +export default async function draft( request: NextApiRequest, response: NextApiResponse ) { - await drupal.preview(request, response) + // Enables Preview mode and Draft mode. + await drupal.preview(request, response, { enable: true }) } From ebf9aa60569a3dee2aed2d2cdd67b4c1f0f5c12d Mon Sep 17 00:00:00 2001 From: JohnAlbin Date: Mon, 11 Mar 2024 23:59:49 +0800 Subject: [PATCH 5/7] feat(pages-starter): add Pages Router-based starter Issue #601 --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + .github/ISSUE_TEMPLATE/feature_request.yml | 1 + .github/ISSUE_TEMPLATE/question.yml | 1 + .github/pull_request_template.md | 1 + MAINTAINING.md | 1 + starters/pages-starter/.env.example | 12 + starters/pages-starter/.eslintrc.json | 4 + starters/pages-starter/.gitignore | 40 ++ starters/pages-starter/.nvmrc | 1 + starters/pages-starter/.prettierignore | 18 + starters/pages-starter/.prettierrc.json | 4 + starters/pages-starter/CHANGELOG.md | 409 ++++++++++++++++++ starters/pages-starter/README.md | 15 + starters/pages-starter/components/Layout.tsx | 15 + .../components/drupal/Article.tsx | 46 ++ .../components/drupal/ArticleTeaser.tsx | 54 +++ .../components/drupal/BasicPage.tsx | 19 + .../components/misc/PreviewAlert.tsx | 35 ++ .../components/navigation/HeaderNav.tsx | 21 + .../components/navigation/Link.tsx | 23 + starters/pages-starter/lib/drupal.ts | 14 + starters/pages-starter/lib/utils.ts | 12 + starters/pages-starter/next.config.js | 16 + starters/pages-starter/package.json | 34 ++ starters/pages-starter/pages/[...slug].tsx | 92 ++++ starters/pages-starter/pages/_app.tsx | 6 + starters/pages-starter/pages/_document.tsx | 13 + .../pages-starter/pages/api/exit-preview.ts | 9 + starters/pages-starter/pages/api/preview.ts | 10 + .../pages-starter/pages/api/revalidate.ts | 29 ++ starters/pages-starter/pages/index.tsx | 57 +++ starters/pages-starter/postcss.config.js | 8 + starters/pages-starter/public/favicon.ico | Bin 0 -> 15086 bytes starters/pages-starter/public/robots.txt | 2 + starters/pages-starter/styles/globals.css | 3 + starters/pages-starter/tailwind.config.ts | 18 + starters/pages-starter/tsconfig.json | 28 ++ 37 files changed, 1072 insertions(+) create mode 100644 starters/pages-starter/.env.example create mode 100644 starters/pages-starter/.eslintrc.json create mode 100644 starters/pages-starter/.gitignore create mode 100644 starters/pages-starter/.nvmrc create mode 100644 starters/pages-starter/.prettierignore create mode 100644 starters/pages-starter/.prettierrc.json create mode 100644 starters/pages-starter/CHANGELOG.md create mode 100644 starters/pages-starter/README.md create mode 100644 starters/pages-starter/components/Layout.tsx create mode 100644 starters/pages-starter/components/drupal/Article.tsx create mode 100644 starters/pages-starter/components/drupal/ArticleTeaser.tsx create mode 100644 starters/pages-starter/components/drupal/BasicPage.tsx create mode 100644 starters/pages-starter/components/misc/PreviewAlert.tsx create mode 100644 starters/pages-starter/components/navigation/HeaderNav.tsx create mode 100644 starters/pages-starter/components/navigation/Link.tsx create mode 100644 starters/pages-starter/lib/drupal.ts create mode 100644 starters/pages-starter/lib/utils.ts create mode 100644 starters/pages-starter/next.config.js create mode 100644 starters/pages-starter/package.json create mode 100644 starters/pages-starter/pages/[...slug].tsx create mode 100644 starters/pages-starter/pages/_app.tsx create mode 100644 starters/pages-starter/pages/_document.tsx create mode 100644 starters/pages-starter/pages/api/exit-preview.ts create mode 100644 starters/pages-starter/pages/api/preview.ts create mode 100644 starters/pages-starter/pages/api/revalidate.ts create mode 100644 starters/pages-starter/pages/index.tsx create mode 100644 starters/pages-starter/postcss.config.js create mode 100644 starters/pages-starter/public/favicon.ico create mode 100644 starters/pages-starter/public/robots.txt create mode 100644 starters/pages-starter/styles/globals.css create mode 100644 starters/pages-starter/tailwind.config.ts create mode 100644 starters/pages-starter/tsconfig.json diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9b447389..15857375 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -19,6 +19,7 @@ body: - next-drupal (NPM package) - basic-starter - graphql-starter + - pages-starter - example-auth - example-blog - example-client diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index f02bd619..7a78bf0c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -14,6 +14,7 @@ body: - next-drupal (NPM package) - basic-starter - graphql-starter + - pages-starter - example-auth - example-blog - example-client diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index 5ff543ca..70a5c2fa 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -14,6 +14,7 @@ body: - next-drupal (NPM package) - basic-starter - graphql-starter + - pages-starter - example-auth - example-blog - example-client diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8436a055..2a4cfbba 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -5,6 +5,7 @@ This pull request is for: (mark with an "x") - [ ] `packages/next-drupal` - [ ] `starters/basic-starter` - [ ] `starters/graphql-starter` +- [ ] `starters/pages-starter` - [ ] Other GitHub Issue: # diff --git a/MAINTAINING.md b/MAINTAINING.md index d342fb96..62d32168 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -168,6 +168,7 @@ The code in the examples repos do not strictly require a versioned release since - [basic-starter](https://github.com/chapter-three/next-drupal-basic-starter/releases) - [graphql-starter](https://github.com/chapter-three/next-drupal-graphql-starter/releases) + - [pages-starter](https://github.com/chapter-three/next-drupal-pages-starter/releases) And then: diff --git a/starters/pages-starter/.env.example b/starters/pages-starter/.env.example new file mode 100644 index 00000000..951ea909 --- /dev/null +++ b/starters/pages-starter/.env.example @@ -0,0 +1,12 @@ +# See https://next-drupal.org/docs/environment-variables + +# Required +NEXT_PUBLIC_DRUPAL_BASE_URL=https://site.example.com +NEXT_IMAGE_DOMAIN=site.example.com + +# Authentication +DRUPAL_CLIENT_ID=Retrieve this from /admin/config/services/consumer +DRUPAL_CLIENT_SECRET=Retrieve this from /admin/config/services/consumer + +# Required for On-demand Revalidation +DRUPAL_REVALIDATE_SECRET=Retrieve this from /admin/config/services/next diff --git a/starters/pages-starter/.eslintrc.json b/starters/pages-starter/.eslintrc.json new file mode 100644 index 00000000..7c1a3add --- /dev/null +++ b/starters/pages-starter/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "extends": "next/core-web-vitals", + "root": true +} diff --git a/starters/pages-starter/.gitignore b/starters/pages-starter/.gitignore new file mode 100644 index 00000000..081b7c17 --- /dev/null +++ b/starters/pages-starter/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# IDE files +/.idea +/.vscode + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/starters/pages-starter/.nvmrc b/starters/pages-starter/.nvmrc new file mode 100644 index 00000000..9a2a0e21 --- /dev/null +++ b/starters/pages-starter/.nvmrc @@ -0,0 +1 @@ +v20 diff --git a/starters/pages-starter/.prettierignore b/starters/pages-starter/.prettierignore new file mode 100644 index 00000000..03c8a68b --- /dev/null +++ b/starters/pages-starter/.prettierignore @@ -0,0 +1,18 @@ +# Ignore everything. +/* + +# Format most files in the root directory. +!/*.js +!/*.ts +!/*.md +!/*.json +# But ignore some. +/package.json +/package-lock.json +/CHANGELOG.md + +# Don't ignore these nested directories. +!/app +!/components +!/lib +!/pages diff --git a/starters/pages-starter/.prettierrc.json b/starters/pages-starter/.prettierrc.json new file mode 100644 index 00000000..3c60a7b5 --- /dev/null +++ b/starters/pages-starter/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "semi": false, + "trailingComma": "es5" +} diff --git a/starters/pages-starter/CHANGELOG.md b/starters/pages-starter/CHANGELOG.md new file mode 100644 index 00000000..b260bf9b --- /dev/null +++ b/starters/pages-starter/CHANGELOG.md @@ -0,0 +1,409 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [1.8.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.7.2...basic-starter@1.8.0) (2022-12-06) + +**Note:** Version bump only for package basic-starter + + + + + +## [1.7.2](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.7.1...basic-starter@1.7.2) (2022-12-06) + +**Note:** Version bump only for package basic-starter + + + + + +## [1.7.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.7.0...basic-starter@1.7.1) (2022-09-07) + +**Note:** Version bump only for package basic-starter + + + + + +# [1.7.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.6.0...basic-starter@1.7.0) (2022-07-29) + + +### Features + +* **basic-starter:** add type to next.config.js ([c1db60d](https://github.com/chapter-three/next-drupal/commit/c1db60d460ec2c0b2f3149d455e8c1b4bcc4a080)) +* **basic-starter:** fix jsonapi params to work with vanilla drupal ([258019f](https://github.com/chapter-three/next-drupal/commit/258019f5bc0fa34e3ce3a824f99b28ea60b5ad30)) +* **basic-starter:** update the example env variables ([1ed83da](https://github.com/chapter-three/next-drupal/commit/1ed83da4c0ec6ef3f0487f43faf1d8a4fdb29858)) + + + + + +# [1.6.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.6.0-rc.0...basic-starter@1.6.0) (2022-06-14) + +**Note:** Version bump only for package basic-starter + + + + + +# [1.6.0-rc.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.5.3-rc.1...basic-starter@1.6.0-rc.0) (2022-06-14) + + +### Features + +* **basic-starter:** update to DrupalClient ([e2dc220](https://github.com/chapter-three/next-drupal/commit/e2dc2202d01a09aba5695ffbbe35d990981d3301)) + + + + + +## [1.5.3-rc.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.5.3-rc.0...basic-starter@1.5.3-rc.1) (2022-06-10) + +**Note:** Version bump only for package basic-starter + + + + + +## [1.5.3-rc.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.5.3-alpha.0...basic-starter@1.5.3-rc.0) (2022-06-06) + +**Note:** Version bump only for package basic-starter + + + + + +## [1.5.3-alpha.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.5.2...basic-starter@1.5.3-alpha.0) (2022-06-02) + + +### Bug Fixes + +* **basic-starter:** rename api pages to .ts ([40456b0](https://github.com/chapter-three/next-drupal/commit/40456b08ae288c441195fe38b8d5008736bfce05)) + + + + + +## [1.5.2](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.5.1...basic-starter@1.5.2) (2022-05-02) + +**Note:** Version bump only for package basic-starter + + + + + +## [1.5.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.5.0...basic-starter@1.5.1) (2022-04-25) + +**Note:** Version bump only for package basic-starter + + + + + +# [1.5.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.4.1-rc.0...basic-starter@1.5.0) (2022-04-19) + + +### Bug Fixes + +* **basic-starter:** rename eslint.json ([844bd93](https://github.com/chapter-three/next-drupal/commit/844bd93b0d4e6a3d24e6e76622067f344e440def)) + + + + + +## [1.4.1-rc.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.4.1-alpha.0...basic-starter@1.4.1-rc.0) (2022-04-19) + +**Note:** Version bump only for package basic-starter + + + + + +## [1.4.1-alpha.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.4.0...basic-starter@1.4.1-alpha.0) (2022-04-18) + + +### Bug Fixes + +* update tests ([0f4d49e](https://github.com/chapter-three/next-drupal/commit/0f4d49e9bb3b8767577bdba4ef52d7e58ad6bf91)) + + + + + +# [1.4.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.3.1...basic-starter@1.4.0) (2022-04-11) + + +### Bug Fixes + +* **basic-starter:** use a tag for exit preview ([8f68023](https://github.com/chapter-three/next-drupal/commit/8f680232a53740f083fd7c208b54f4293ad0f58a)) + + +### Features + +* **basic-starter:** show only published articles on index ([d209af9](https://github.com/chapter-three/next-drupal/commit/d209af9c08b4db12f8c2f7adfb7adfc3840a8f02)) +* **basic-starter:** update starter ([ad6afa9](https://github.com/chapter-three/next-drupal/commit/ad6afa999b59f49d5f6b199aaa4b3e3c1683c352)) + + + + + +## [1.3.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.3.0...basic-starter@1.3.1) (2022-03-28) + +**Note:** Version bump only for package basic-starter + + + + + +# [1.3.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.2.0...basic-starter@1.3.0) (2022-02-24) + + +### Bug Fixes + +* **basic-starter:** fix type for private in package.json ([b1a6e90](https://github.com/chapter-three/next-drupal/commit/b1a6e907e22de61354b42b0126e5a084bd90f57b)) +* **basic-starter:** update @tailwindcss/typography ([aa70f4e](https://github.com/chapter-three/next-drupal/commit/aa70f4ee6287e7fcc6ce1352114a4ef24474e404)) +* **basic-starter:** update typescript ([3eda875](https://github.com/chapter-three/next-drupal/commit/3eda8755dbf2c904e9253bff7df67a9992bbdc12)) + + +### Features + +* bump all examples to next 12.1.0 ([00b15f2](https://github.com/chapter-three/next-drupal/commit/00b15f2b308a0a9fcb298789a9ca712f4efa7eff)) +* **basic-starter:** simplify starter by removing menus ([7ce44ac](https://github.com/chapter-three/next-drupal/commit/7ce44ac11b628f06849b09a1831069df5da2a926)) +* **basic-starter:** use server-side menus ([58f1150](https://github.com/chapter-three/next-drupal/commit/58f1150e750d860cb62b60f28edca3673dbb3c68)) + + + + + +# [1.2.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.1.1...basic-starter@1.2.0) (2022-01-17) + + +### Features + +* **basic-starter:** update tailwind and dependencies ([5de7337](https://github.com/chapter-three/next-drupal/commit/5de7337c7372afe44692b3ba49bcf10afdf9cfd6)) + + + + + +## [1.1.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.1.0...basic-starter@1.1.1) (2022-01-12) + +**Note:** Version bump only for package basic-starter + + + + + +# [1.1.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.0.0...basic-starter@1.1.0) (2021-12-21) + + +### Features + +* **basic-starter:** bump next to 12 ([3185cd4](https://github.com/chapter-three/next-drupal/commit/3185cd4f720e87e91d2a03e335729a6ae8df4e78)) + + + + + +# [1.0.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.7.0...basic-starter@1.0.0) (2021-12-03) + +**Note:** Version bump only for package basic-starter + + + + + +# [0.7.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.6.0...basic-starter@0.7.0) (2021-11-24) + + +### Features + +* **basic-starter:** update dependencies and components ([233549b](https://github.com/chapter-three/next-drupal/commit/233549b1c2c3f401fac9b4290dcbe53682670d2f)) + + + + + +# [0.6.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.6.0-alpha.0...basic-starter@0.6.0) (2021-11-01) + +**Note:** Version bump only for package basic-starter + + + + + +# [0.6.0-alpha.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.5.2...basic-starter@0.6.0-alpha.0) (2021-11-01) + +**Note:** Version bump only for package basic-starter + + + + + +## [0.5.2](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.5.1...basic-starter@0.5.2) (2021-10-14) + +**Note:** Version bump only for package basic-starter + + + + + +## [0.5.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.5.0...basic-starter@0.5.1) (2021-10-14) + +**Note:** Version bump only for package basic-starter + + + + + +# [0.5.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.4.2...basic-starter@0.5.0) (2021-10-13) + + +### Bug Fixes + +* rename repo links ([48d52dd](https://github.com/chapter-three/next-drupal/commit/48d52dde79f69396ef706d152c03670117b6a480)) +* **basic-starter:** update next ([ea46504](https://github.com/chapter-three/next-drupal/commit/ea465044bec1865bab850f588e856be2fcaaf34c)) + + +### Features + +* **basic-starter:** update the basic starter ([a7efdcd](https://github.com/chapter-three/next-drupal/commit/a7efdcdf2fb38057027aad12e11e63ba21318b32)) + + + + + +## [0.4.2](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.4.1...basic-starter@0.4.2) (2021-08-11) + +**Note:** Version bump only for package basic-starter + + + + + +## [0.4.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.4.0...basic-starter@0.4.1) (2021-08-07) + +**Note:** Version bump only for package basic-starter + + + + + +# [0.4.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.9...basic-starter@0.4.0) (2021-06-22) + + +### Bug Fixes + +* **basic-starter:** add props to NodeMeta ([613a2e1](https://github.com/chapter-three/next-drupal/commit/613a2e1c732b2fe94538ffdd66e42d3af60d0088)) + + +### Features + +* **basic-starter:** update the basic starter ([db2f99c](https://github.com/chapter-three/next-drupal/commit/db2f99c3872a7e46cedcad66650b6f03fd645dbb)) + + + + + +## [0.3.9](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.8...basic-starter@0.3.9) (2021-06-16) + +**Note:** Version bump only for package basic-starter + + + + + +## [0.3.8](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.7...basic-starter@0.3.8) (2021-06-16) + +**Note:** Version bump only for package basic-starter + + + + + +## [0.3.7](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.6...basic-starter@0.3.7) (2021-06-15) + +**Note:** Version bump only for package basic-starter + + + + + +## [0.3.6](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.5...basic-starter@0.3.6) (2021-06-14) + +**Note:** Version bump only for package basic-starter + + + + + +## [0.3.5](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.4...basic-starter@0.3.5) (2021-06-13) + +**Note:** Version bump only for package basic-starter + + + + + +## [0.3.4](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.3...basic-starter@0.3.4) (2021-06-13) + +**Note:** Version bump only for package basic-starter + + + + + +## [0.3.3](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.2...basic-starter@0.3.3) (2021-06-13) + +**Note:** Version bump only for package basic-starter + + + + + +## [0.3.2](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.1...basic-starter@0.3.2) (2021-06-11) + +**Note:** Version bump only for package basic-starter + + + + + +## [0.3.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.0...basic-starter@0.3.1) (2021-06-10) + +**Note:** Version bump only for package basic-starter + + + + + +# [0.3.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.2.0...basic-starter@0.3.0) (2021-05-17) + + +### Features + +* add getEntityByPath ([072ead7](https://github.com/chapter-three/next-drupal/commit/072ead7ecc3b7f158e4b81e03d17f0bf1a5b511c)) + + + + + +# [0.2.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.1.0...basic-starter@0.2.0) (2021-05-17) + + +### Features + +* deserialize entities by default ([8b53ae2](https://github.com/chapter-three/next-drupal/commit/8b53ae222717b8983568194373be04903944a032)) + + + + + +# 0.1.0 (2021-05-07) + + +### Features + +* add basic-starter ([92b746a](https://github.com/chapter-three/next-drupal/commit/92b746aef6b59d893cb3c2f49d35d7dcc733c7c8)) diff --git a/starters/pages-starter/README.md b/starters/pages-starter/README.md new file mode 100644 index 00000000..2acfc085 --- /dev/null +++ b/starters/pages-starter/README.md @@ -0,0 +1,15 @@ +# Pages Starter + +A simple starter for building your site with Next.js' Pages Router and Drupal. + +## How to use + +`npx create-next-app -e https://github.com/chapter-three/next-drupal-pages-starter` + +## Deploy to Vercel + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fchapter-three%2Fnext-drupal-pages-starter&env=NEXT_PUBLIC_DRUPAL_BASE_URL,NEXT_IMAGE_DOMAIN,DRUPAL_CLIENT_ID,DRUPAL_CLIENT_SECRET&envDescription=Learn%20more%20about%20environment%20variables&envLink=https%3A%2F%2Fnext-drupal.org%2Fdocs%2Fenvironment-variables&project-name=next-drupal&demo-title=Next.js%20for%20Drupal&demo-description=A%20next-generation%20front-end%20for%20your%20Drupal%20site.&demo-url=https%3A%2F%2Fdemo.next-drupal.org&demo-image=https%3A%2F%2Fnext-drupal.org%2Fimages%2Fdemo-screenshot.jpg) + +## Documentation + +See https://next-drupal.org diff --git a/starters/pages-starter/components/Layout.tsx b/starters/pages-starter/components/Layout.tsx new file mode 100644 index 00000000..cb1f4012 --- /dev/null +++ b/starters/pages-starter/components/Layout.tsx @@ -0,0 +1,15 @@ +import { HeaderNav } from "@/components/navigation/HeaderNav" +import { PreviewAlert } from "@/components/misc/PreviewAlert" +import type { ReactNode } from "react" + +export function Layout({ children }: { children: ReactNode }) { + return ( + <> + +
+ +
{children}
+
+ + ) +} diff --git a/starters/pages-starter/components/drupal/Article.tsx b/starters/pages-starter/components/drupal/Article.tsx new file mode 100644 index 00000000..b4d3d234 --- /dev/null +++ b/starters/pages-starter/components/drupal/Article.tsx @@ -0,0 +1,46 @@ +import Image from "next/image" +import { absoluteUrl, formatDate } from "@/lib/utils" +import type { DrupalNode } from "next-drupal" + +interface ArticleProps { + node: DrupalNode +} + +export function Article({ node, ...props }: ArticleProps) { + return ( +
+

{node.title}

+
+ {node.uid?.display_name ? ( + + Posted by{" "} + {node.uid?.display_name} + + ) : null} + - {formatDate(node.created)} +
+ {node.field_image && ( +
+ {node.field_image.resourceIdObjMeta.alt + {node.field_image.resourceIdObjMeta.title && ( +
+ {node.field_image.resourceIdObjMeta.title} +
+ )} +
+ )} + {node.body?.processed && ( +
+ )} +
+ ) +} diff --git a/starters/pages-starter/components/drupal/ArticleTeaser.tsx b/starters/pages-starter/components/drupal/ArticleTeaser.tsx new file mode 100644 index 00000000..8efeac62 --- /dev/null +++ b/starters/pages-starter/components/drupal/ArticleTeaser.tsx @@ -0,0 +1,54 @@ +import Image from "next/image" +import { Link } from "@/components/navigation/Link" +import { absoluteUrl, formatDate } from "@/lib/utils" +import type { DrupalNode } from "next-drupal" + +interface ArticleTeaserProps { + node: DrupalNode +} + +export function ArticleTeaser({ node, ...props }: ArticleTeaserProps) { + return ( +
+ +

{node.title}

+ +
+ {node.uid?.display_name ? ( + + Posted by{" "} + {node.uid?.display_name} + + ) : null} + - {formatDate(node.created)} +
+ {node.field_image && ( +
+ {node.field_image.resourceIdObjMeta.alt} +
+ )} + + Read article + + + + +
+ ) +} diff --git a/starters/pages-starter/components/drupal/BasicPage.tsx b/starters/pages-starter/components/drupal/BasicPage.tsx new file mode 100644 index 00000000..88d7f00b --- /dev/null +++ b/starters/pages-starter/components/drupal/BasicPage.tsx @@ -0,0 +1,19 @@ +import type { DrupalNode } from "next-drupal" + +interface BasicPageProps { + node: DrupalNode +} + +export function BasicPage({ node, ...props }: BasicPageProps) { + return ( +
+

{node.title}

+ {node.body?.processed && ( +
+ )} +
+ ) +} diff --git a/starters/pages-starter/components/misc/PreviewAlert.tsx b/starters/pages-starter/components/misc/PreviewAlert.tsx new file mode 100644 index 00000000..edce5720 --- /dev/null +++ b/starters/pages-starter/components/misc/PreviewAlert.tsx @@ -0,0 +1,35 @@ +import { useEffect, useState } from "react" +import { useRouter } from "next/router" + +export function PreviewAlert() { + const router = useRouter() + const isPreview = router.isPreview + const [showPreviewAlert, setShowPreviewAlert] = useState(false) + + useEffect(() => { + setShowPreviewAlert(isPreview && window.top === window.self) + }, [isPreview]) + + if (!showPreviewAlert) { + return null + } + + function buttonHandler() { + void fetch("/api/exit-preview") + setShowPreviewAlert(false) + } + + return ( +
+

+ This page is a preview.{" "} + +

+
+ ) +} diff --git a/starters/pages-starter/components/navigation/HeaderNav.tsx b/starters/pages-starter/components/navigation/HeaderNav.tsx new file mode 100644 index 00000000..bccb4e4c --- /dev/null +++ b/starters/pages-starter/components/navigation/HeaderNav.tsx @@ -0,0 +1,21 @@ +import { Link } from "@/components/navigation/Link" + +export function HeaderNav() { + return ( +
+
+ + Next.js for Drupal + + + Read the docs + +
+
+ ) +} diff --git a/starters/pages-starter/components/navigation/Link.tsx b/starters/pages-starter/components/navigation/Link.tsx new file mode 100644 index 00000000..23dbc4c0 --- /dev/null +++ b/starters/pages-starter/components/navigation/Link.tsx @@ -0,0 +1,23 @@ +import { forwardRef } from "react" +import NextLink from "next/link" +import type { AnchorHTMLAttributes, ReactNode } from "react" +import type { LinkProps as NextLinkProps } from "next/link" + +type LinkProps = NextLinkProps & + Omit, keyof NextLinkProps> & { + children?: ReactNode + } + +export const Link = forwardRef( + function LinkWithRef( + { + // Turn next/link prefetching off by default. + // @see https://github.com/vercel/next.js/discussions/24009 + prefetch = false, + ...rest + }, + ref + ) { + return + } +) diff --git a/starters/pages-starter/lib/drupal.ts b/starters/pages-starter/lib/drupal.ts new file mode 100644 index 00000000..9b2f40c0 --- /dev/null +++ b/starters/pages-starter/lib/drupal.ts @@ -0,0 +1,14 @@ +import { NextDrupalPages } from "next-drupal" + +const baseUrl = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL as string +const clientId = process.env.DRUPAL_CLIENT_ID as string +const clientSecret = process.env.DRUPAL_CLIENT_SECRET as string + +export const drupal = new NextDrupalPages(baseUrl, { + auth: { + clientId, + clientSecret, + }, + useDefaultEndpoints: true, + // debug: true, +}) diff --git a/starters/pages-starter/lib/utils.ts b/starters/pages-starter/lib/utils.ts new file mode 100644 index 00000000..d83a0d73 --- /dev/null +++ b/starters/pages-starter/lib/utils.ts @@ -0,0 +1,12 @@ +export function formatDate(input: string): string { + const date = new Date(input) + return date.toLocaleDateString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + }) +} + +export function absoluteUrl(input: string) { + return `${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}${input}` +} diff --git a/starters/pages-starter/next.config.js b/starters/pages-starter/next.config.js new file mode 100644 index 00000000..0a7fabac --- /dev/null +++ b/starters/pages-starter/next.config.js @@ -0,0 +1,16 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + images: { + remotePatterns: [ + { + // protocol: 'https', + hostname: process.env.NEXT_IMAGE_DOMAIN, + // port: '', + // pathname: '/sites/default/files/**', + }, + ], + }, +} + +module.exports = nextConfig diff --git a/starters/pages-starter/package.json b/starters/pages-starter/package.json new file mode 100644 index 00000000..a910663f --- /dev/null +++ b/starters/pages-starter/package.json @@ -0,0 +1,34 @@ +{ + "name": "pages-starter", + "version": "2.0.0-alpha.0", + "private": true, + "license": "MIT", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "preview": "next build && next start", + "lint": "next lint", + "format": "prettier --write .", + "format:check": "prettier --check ." + }, + "dependencies": { + "next": "^14.2.2", + "next-drupal": "^2.0.0-alpha.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@tailwindcss/typography": "^0.5.12", + "@types/node": "^20.12.7", + "@types/react": "^18.2.79", + "@types/react-dom": "^18.2.25", + "autoprefixer": "^10.4.19", + "eslint": "^8.57.0", + "eslint-config-next": "^14.2.2", + "postcss": "^8.4.38", + "prettier": "^3.2.5", + "tailwindcss": "^3.4.3", + "typescript": "^5.4.5" + } +} diff --git a/starters/pages-starter/pages/[...slug].tsx b/starters/pages-starter/pages/[...slug].tsx new file mode 100644 index 00000000..9894d5f0 --- /dev/null +++ b/starters/pages-starter/pages/[...slug].tsx @@ -0,0 +1,92 @@ +import Head from "next/head" +import { Article } from "@/components/drupal/Article" +import { BasicPage } from "@/components/drupal/BasicPage" +import { Layout } from "@/components/Layout" +import { drupal } from "@/lib/drupal" +import type { + GetStaticPaths, + GetStaticProps, + InferGetStaticPropsType, +} from "next" +import type { DrupalNode } from "next-drupal" + +const RESOURCE_TYPES = ["node--page", "node--article"] + +export const getStaticPaths = (async (context) => { + return { + paths: await drupal.getStaticPathsFromContext(RESOURCE_TYPES, context), + fallback: "blocking", + } +}) satisfies GetStaticPaths + +export const getStaticProps = (async (context) => { + const path = await drupal.translatePathFromContext(context) + + if (!path) { + return { + notFound: true, + } + } + + const type = path?.jsonapi?.resourceName + + let params = {} + if (type === "node--article") { + params = { + include: "field_image,uid", + } + } + + const resource = await drupal.getResourceFromContext( + path, + context, + { + params, + } + ) + + // At this point, we know the path exists and it points to a resource. + // If we receive an error, it means something went wrong on Drupal. + // We throw an error to tell revalidation to skip this for now. + // Revalidation can try again on next request. + if (!resource) { + throw new Error(`Failed to fetch resource: ${path?.jsonapi?.individual}`) + } + + // If we're not in preview mode and the resource is not published, + // Return page not found. + if (!context.preview && resource?.status === false) { + return { + notFound: true, + } + } + + return { + props: { + resource, + }, + } +}) satisfies GetStaticProps<{ + resource: DrupalNode +}> + +export default function NodePage({ + resource, +}: InferGetStaticPropsType) { + if (!resource) return null + + return ( + + + {resource.title} + + + {resource.type === "node--page" && } + {resource.type === "node--article" &&
} + + ) +} diff --git a/starters/pages-starter/pages/_app.tsx b/starters/pages-starter/pages/_app.tsx new file mode 100644 index 00000000..70739e9b --- /dev/null +++ b/starters/pages-starter/pages/_app.tsx @@ -0,0 +1,6 @@ +import "@/styles/globals.css" +import type { AppProps } from "next/app" + +export default function App({ Component, pageProps }: AppProps) { + return +} diff --git a/starters/pages-starter/pages/_document.tsx b/starters/pages-starter/pages/_document.tsx new file mode 100644 index 00000000..097cb7ff --- /dev/null +++ b/starters/pages-starter/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from "next/document" + +export default function Document() { + return ( + + + +
+ + + + ) +} diff --git a/starters/pages-starter/pages/api/exit-preview.ts b/starters/pages-starter/pages/api/exit-preview.ts new file mode 100644 index 00000000..f8847b39 --- /dev/null +++ b/starters/pages-starter/pages/api/exit-preview.ts @@ -0,0 +1,9 @@ +import { drupal } from "@/lib/drupal" +import type { NextApiRequest, NextApiResponse } from "next" + +export default async function exit( + request: NextApiRequest, + response: NextApiResponse +) { + await drupal.previewDisable(request, response) +} diff --git a/starters/pages-starter/pages/api/preview.ts b/starters/pages-starter/pages/api/preview.ts new file mode 100644 index 00000000..a0733440 --- /dev/null +++ b/starters/pages-starter/pages/api/preview.ts @@ -0,0 +1,10 @@ +import { drupal } from "@/lib/drupal" +import type { NextApiRequest, NextApiResponse } from "next" + +export default async function draft( + request: NextApiRequest, + response: NextApiResponse +) { + // Enables Preview mode and Draft mode. + await drupal.preview(request, response, { enable: true }) +} diff --git a/starters/pages-starter/pages/api/revalidate.ts b/starters/pages-starter/pages/api/revalidate.ts new file mode 100644 index 00000000..368a16c8 --- /dev/null +++ b/starters/pages-starter/pages/api/revalidate.ts @@ -0,0 +1,29 @@ +import type { NextApiRequest, NextApiResponse } from "next" + +export default async function handler( + request: NextApiRequest, + response: NextApiResponse +) { + let path = request.query.path as string + const secret = request.query.secret as string + + // Validate secret. + if (secret !== process.env.DRUPAL_REVALIDATE_SECRET) { + return response.status(401).json({ message: "Invalid secret." }) + } + + // Validate path. + if (!path) { + return response.status(400).json({ message: "Invalid path." }) + } + + try { + await response.revalidate(path) + + return response.json({}) + } catch (error) { + return response.status(404).json({ + message: (error as Error).message, + }) + } +} diff --git a/starters/pages-starter/pages/index.tsx b/starters/pages-starter/pages/index.tsx new file mode 100644 index 00000000..e5963b98 --- /dev/null +++ b/starters/pages-starter/pages/index.tsx @@ -0,0 +1,57 @@ +import Head from "next/head" +import { ArticleTeaser } from "@/components/drupal/ArticleTeaser" +import { Layout } from "@/components/Layout" +import { drupal } from "@/lib/drupal" +import type { InferGetStaticPropsType, GetStaticProps } from "next" +import type { DrupalNode } from "next-drupal" + +export const getStaticProps = (async (context) => { + const nodes = await drupal.getResourceCollectionFromContext( + "node--article", + context, + { + params: { + "filter[status]": 1, + "fields[node--article]": "title,path,field_image,uid,created", + include: "field_image,uid", + sort: "-created", + }, + } + ) + + return { + props: { + nodes, + }, + } +}) satisfies GetStaticProps<{ + nodes: DrupalNode[] +}> + +export default function Home({ + nodes, +}: InferGetStaticPropsType) { + return ( + + + Next.js for Drupal + + +

Latest Articles.

+ {nodes?.length ? ( + nodes.map((node) => ( +
+ +
+
+ )) + ) : ( +

No nodes found

+ )} +
+ ) +} diff --git a/starters/pages-starter/postcss.config.js b/starters/pages-starter/postcss.config.js new file mode 100644 index 00000000..3fa0a951 --- /dev/null +++ b/starters/pages-starter/postcss.config.js @@ -0,0 +1,8 @@ +// If you want to use other PostCSS plugins, see the following: +// https://tailwindcss.com/docs/using-with-preprocessors +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/starters/pages-starter/public/favicon.ico b/starters/pages-starter/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ea2f437d9db6552726693be6cc2943a32dc5a964 GIT binary patch literal 15086 zcmdU$X^c%<7ROI1lv$Z)$~+GtJY$|F!80Yq2f; >NSTKFL46_E}iL5mO)AEZS| zhz??kp>#q_Pa+X9S9!)q#7v>f>h;_1zJ2P{J?EY~6zP?$I_K=Y*ZTkWKIiUX?Ol$O z;bc0!dO3Q{aq`JLhPRsE;xZ>o<~Kd63*DEFk1M;XdugYEZ_Oa-Nh{-%0D^{MKAR3)TR78`8wfiHZP zw^UGo?y4J9e^;e_!*cd3hmYSYR;;*T(V|5?L_wL<$ z@a);MWqJ=*I%S|bJEdBpnlwj0+s2I>%gK``wdqCHu3cM_nVAU|n5l~P2F}l_<kDo{eQU1fr(;a=<7HsJ2X}tEcJ1Wh!-s|o-vjk==X>vm4I3s;pFTC^uUxqj`ki_GM4sUaXDiGB z1M$82Uj9v*H1V}>)TogZ6ciZlZ{NNZe!tdVl`2)FLWK&^XPX}43TK2l_;J;Qe7O1F zym?dn_`_}G%9Zlv%NN6c>(;H3m6c_%YSyeNGiT0}YSpSGgdO1uXSn~M`bQ)!w4bp2 zM~)mR&!0ax+^=4}D(&01k7CT8JzEYQJSbJGR*j}5zyr>3|BGrOYY&6v=6~zfEos`c zX&DZrojG&HaA%G)b?Ve;nGPK~n0}ObC*M#4ylq)H!=3ekwl>EI7{*43fg-In$8Z`Q0?)JKaJE#$(53x?l^4Y2;5_qa=u@U!I#kb9lRMnmBQyS&P7(aji?2E@j## zjr`%B)O^j$pT4G5t5(9C3@I)y7W&6n^-m*zxbqHT;sm98`L}J`RyJ+gWVrKZ=gytJ z<5C*=!#yp(Uya>F=k$yR-MV%2ZId+ew=q3mEWdZ)Zhz*&j~_oaZBM^4ckbLke`(}T z|C#dm75!-RX7}#hQoVZhZ^a+(DbF7)|GRhZO3$7>1G)1))71WU{=gh4rTH8DS;x$u zKR+(_H1fCeH|C(sMMGiRa`ZP39z3v3FMIdy73RY>Kj@mK_J=dvk%ZST+1c5qkGy{U zx|}+7D%5wQIHrEXhYy$c@86rcA3l6Ix*o9kA+L7*qQ%WxETQ!y82nC&L)EHPD^$B% zUiIqLGizbiz|8Y(dIDGP`Y}Uo6I#C~fRzltcsAbkyV5Z+iO}(r@5nz^`~?0;xyP>* zBVOAEPQS}LezcU8u3`m?0g{%YrLZ+zejpZEr&yd_fs z{vXe8#A+Aj;Qds;%lR3RqAbz38P#IPX~naS80-j1fer>-^K`?8s}ki8FWvd|Yv9`X z(Q(vF4ZjY0PVN6)i%8FMoC2=;*1Xp|(slOhy^@?x$-`L;oC4Bg@G2r*c{6aj>tpN5 z;+1;ab`YNRPqdv1BJF0|(Y7mPjDBr*KH?nJp{mhyTG{%LoX}TyR$ZXFSM{dq8& zlzY<1qYP!!(ZBP1WF5anwNUl5s@5`4DMQ&UBSwt)K^KF`@@T3))~IqGD;{QjQmLp-B2x4r?6?WX3a8t6!YfI zbNj$2zJrAR>bk1iE}{Frefx4=OZM#9BQ=Vj99`uI<4xp3H{u#VMAfx2z+gK zKRYnI-veXY>Qu1VpU^X2X8&Ztf(3rqHXST5t@PKMKU8ggsVn=C?59qfHqCc$e#(?7 z=DgFsefz@1o4^7S;WxfnwG^iNnX?$gw1_{)+7Is3sgn>dx_|$^aCXUU8*mRM*j-c$ z1In2_{YQ@;32{vKeik;g4SPYHUs$+sp~0?SzrJrfxp2S)oBlGeU*jBzAO39J3n50y z`48ev#E|mx@}y3kI`P^8OxvFami5E$-o3l9N6C4Ke*O9xABz_+ma}KihK+qOUc~Kd zzz3tSurRuxMT{*!Ki};CvqzzR%pN+wJTYo7$Z9lxVKtS|S~UH`6KyPCe0AnTGP zOG?=#i66TCsWW{X?SAy=QQ_Q^n`aXEV2Aqt=zrKl;+)KrCr@PX;K8M^;gJMB*!%{{ z_;RPw{`74K{mjy(ONDb&?lRmbfp6z)ar+;O&-h0F=EY6|-}XOo`(MUY&UCT|JY>j_ znAqSm_Os_l{}W=*qfl+YjywL)*7T?JtwCp^_{}p$>bx_s99&iH_@lRGEc6|JBjtVT zokYHS`~^Sd`A32rR!1q{@cJ12bJ@bbRw$%5zwP#FJo@PuU zjc3-pqeqX9`j3>w2HRbcXOqu4fzzi?NB=)!#flZOYSk*~+_`hCu@l?-ZhPh=*mJ&& z@s9JVy?ghTEnBw4Ix~%JsW}GroIPg#df>nT88c>#Ig^G@&Lz9CBDQ7hIfKf%80LhG z(X2-T>`U=)-MY0|r=L4_PB@ncUVuHerP_~m;IU)JWc>K?QLdb+mU@4D zAIvA*|69Yi$NbuzrgrxImHB==7+y^KY`-5ZZocQh@7Lp#viAGM9^o0EF@Har4la3= q@!D#Vku3e|`}>XLQ6?(ItsJLFQwlOQrkn9qqnVx?n@?G6u>CIsZs3vt literal 0 HcmV?d00001 diff --git a/starters/pages-starter/public/robots.txt b/starters/pages-starter/public/robots.txt new file mode 100644 index 00000000..14267e90 --- /dev/null +++ b/starters/pages-starter/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / \ No newline at end of file diff --git a/starters/pages-starter/styles/globals.css b/starters/pages-starter/styles/globals.css new file mode 100644 index 00000000..b5c61c95 --- /dev/null +++ b/starters/pages-starter/styles/globals.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/starters/pages-starter/tailwind.config.ts b/starters/pages-starter/tailwind.config.ts new file mode 100644 index 00000000..c7f5c8a1 --- /dev/null +++ b/starters/pages-starter/tailwind.config.ts @@ -0,0 +1,18 @@ +import type { Config } from "tailwindcss" + +const config: Config = { + content: [ + "./pages/**/*.{js,ts,jsx,tsx,mdx}", + "./components/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: {}, + }, + variants: { + extend: {}, + }, + plugins: [require("@tailwindcss/typography")], +} + +export default config diff --git a/starters/pages-starter/tsconfig.json b/starters/pages-starter/tsconfig.json new file mode 100644 index 00000000..23ba4fd5 --- /dev/null +++ b/starters/pages-starter/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} From 3dd4b43fdc92d1ee9c24ee8bcf01985c47ab4d84 Mon Sep 17 00:00:00 2001 From: JohnAlbin Date: Mon, 22 Apr 2024 13:01:46 +0800 Subject: [PATCH 6/7] feat(basic-starter): upgrade starters to App Router Fixes #601 --- starters/basic-starter/README.md | 2 +- starters/basic-starter/app/[...slug]/page.tsx | 129 ++++++++++++++ .../app/api/disable-draft/route.ts | 6 + starters/basic-starter/app/api/draft/route.ts | 7 + .../basic-starter/app/api/revalidate/route.ts | 28 +++ starters/basic-starter/app/layout.tsx | 37 ++++ .../{pages/index.tsx => app/page.tsx} | 37 +--- starters/basic-starter/components/Layout.tsx | 15 -- .../Client.tsx} | 27 +-- .../components/misc/DraftAlert/index.tsx | 13 ++ starters/basic-starter/lib/drupal.ts | 5 +- starters/basic-starter/pages/[...slug].tsx | 92 ---------- starters/basic-starter/pages/_app.tsx | 6 - starters/basic-starter/pages/_document.tsx | 13 -- .../basic-starter/pages/api/exit-preview.ts | 9 - starters/basic-starter/pages/api/preview.ts | 10 -- .../basic-starter/pages/api/revalidate.ts | 29 ---- .../[...slug].tsx => app/[...slug]/page.tsx} | 160 ++++++++++-------- .../app/api/disable-draft/route.ts | 6 + .../graphql-starter/app/api/draft/route.ts | 7 + .../app/api/revalidate/route.ts | 28 +++ starters/graphql-starter/app/layout.tsx | 37 ++++ .../{pages/index.tsx => app/page.tsx} | 35 +--- .../graphql-starter/components/Layout.tsx | 15 -- .../Client.tsx} | 27 +-- .../components/misc/DraftAlert/index.tsx | 13 ++ starters/graphql-starter/pages/_app.tsx | 6 - starters/graphql-starter/pages/_document.tsx | 13 -- .../graphql-starter/pages/api/exit-preview.ts | 9 - starters/graphql-starter/pages/api/preview.ts | 10 -- .../graphql-starter/pages/api/revalidate.ts | 29 ---- 31 files changed, 447 insertions(+), 413 deletions(-) create mode 100644 starters/basic-starter/app/[...slug]/page.tsx create mode 100644 starters/basic-starter/app/api/disable-draft/route.ts create mode 100644 starters/basic-starter/app/api/draft/route.ts create mode 100644 starters/basic-starter/app/api/revalidate/route.ts create mode 100644 starters/basic-starter/app/layout.tsx rename starters/basic-starter/{pages/index.tsx => app/page.tsx} (50%) delete mode 100644 starters/basic-starter/components/Layout.tsx rename starters/basic-starter/components/misc/{PreviewAlert.tsx => DraftAlert/Client.tsx} (52%) create mode 100644 starters/basic-starter/components/misc/DraftAlert/index.tsx delete mode 100644 starters/basic-starter/pages/[...slug].tsx delete mode 100644 starters/basic-starter/pages/_app.tsx delete mode 100644 starters/basic-starter/pages/_document.tsx delete mode 100644 starters/basic-starter/pages/api/exit-preview.ts delete mode 100644 starters/basic-starter/pages/api/preview.ts delete mode 100644 starters/basic-starter/pages/api/revalidate.ts rename starters/graphql-starter/{pages/[...slug].tsx => app/[...slug]/page.tsx} (51%) create mode 100644 starters/graphql-starter/app/api/disable-draft/route.ts create mode 100644 starters/graphql-starter/app/api/draft/route.ts create mode 100644 starters/graphql-starter/app/api/revalidate/route.ts create mode 100644 starters/graphql-starter/app/layout.tsx rename starters/graphql-starter/{pages/index.tsx => app/page.tsx} (61%) delete mode 100644 starters/graphql-starter/components/Layout.tsx rename starters/graphql-starter/components/misc/{PreviewAlert.tsx => DraftAlert/Client.tsx} (52%) create mode 100644 starters/graphql-starter/components/misc/DraftAlert/index.tsx delete mode 100644 starters/graphql-starter/pages/_app.tsx delete mode 100644 starters/graphql-starter/pages/_document.tsx delete mode 100644 starters/graphql-starter/pages/api/exit-preview.ts delete mode 100644 starters/graphql-starter/pages/api/preview.ts delete mode 100644 starters/graphql-starter/pages/api/revalidate.ts diff --git a/starters/basic-starter/README.md b/starters/basic-starter/README.md index 0075ec73..03dfcfb4 100644 --- a/starters/basic-starter/README.md +++ b/starters/basic-starter/README.md @@ -1,6 +1,6 @@ # Basic Starter -A simple starter for building your site with Next.js' Pages Router and Drupal. +A simple starter for building your site with Next.js and Drupal. ## How to use diff --git a/starters/basic-starter/app/[...slug]/page.tsx b/starters/basic-starter/app/[...slug]/page.tsx new file mode 100644 index 00000000..4e61889c --- /dev/null +++ b/starters/basic-starter/app/[...slug]/page.tsx @@ -0,0 +1,129 @@ +import { draftMode } from "next/headers" +import { notFound } from "next/navigation" +import { getDraftData } from "next-drupal/draft" +import { Article } from "@/components/drupal/Article" +import { BasicPage } from "@/components/drupal/BasicPage" +import { drupal } from "@/lib/drupal" +import type { Metadata, ResolvingMetadata } from "next" +import type { DrupalNode, JsonApiParams } from "next-drupal" + +async function getNode(slug: string[]) { + const path = `/${slug.join("/")}` + + const params: JsonApiParams = {} + + const draftData = getDraftData() + + if (draftData.path === path) { + params.resourceVersion = draftData.resourceVersion + } + + // Translating the path also allows us to discover the entity type. + const translatedPath = await drupal.translatePath(path) + + if (!translatedPath) { + throw new Error("Resource not found", { cause: "NotFound" }) + } + + const type = translatedPath.jsonapi?.resourceName! + const uuid = translatedPath.entity.uuid + + if (type === "node--article") { + params.include = "field_image,uid" + } + + const resource = await drupal.getResource(type, uuid, { + params, + }) + + if (!resource) { + throw new Error( + `Failed to fetch resource: ${translatedPath?.jsonapi?.individual}`, + { + cause: "DrupalError", + } + ) + } + + return resource +} + +type NodePageParams = { + slug: string[] +} +type NodePageProps = { + params: NodePageParams + searchParams: { [key: string]: string | string[] | undefined } +} + +export async function generateMetadata( + { params: { slug } }: NodePageProps, + parent: ResolvingMetadata +): Promise { + let node + try { + node = await getNode(slug) + } catch (e) { + // If we fail to fetch the node, don't return any metadata. + return {} + } + + return { + title: node.title, + } +} + +const RESOURCE_TYPES = ["node--page", "node--article"] + +export async function generateStaticParams(): Promise { + const resources = await drupal.getResourceCollectionPathSegments( + RESOURCE_TYPES, + { + // The pathPrefix will be removed from the returned path segments array. + // pathPrefix: "/blog", + // The list of locales to return. + // locales: ["en", "es"], + // The default locale. + // defaultLocale: "en", + } + ) + + return resources.map((resource) => { + // resources is an array containing objects like: { + // path: "/blog/some-category/a-blog-post", + // type: "node--article", + // locale: "en", // or `undefined` if no `locales` requested. + // segments: ["blog", "some-category", "a-blog-post"], + // } + return { + slug: resource.segments, + } + }) +} + +export default async function NodePage({ + params: { slug }, + searchParams, +}: NodePageProps) { + const isDraftMode = draftMode().isEnabled + + let node + try { + node = await getNode(slug) + } catch (error) { + // If getNode throws an error, tell Next.js the path is 404. + notFound() + } + + // If we're not in draft mode and the resource is not published, return a 404. + if (!isDraftMode && node?.status === false) { + notFound() + } + + return ( + <> + {node.type === "node--page" && } + {node.type === "node--article" &&
} + + ) +} diff --git a/starters/basic-starter/app/api/disable-draft/route.ts b/starters/basic-starter/app/api/disable-draft/route.ts new file mode 100644 index 00000000..81900948 --- /dev/null +++ b/starters/basic-starter/app/api/disable-draft/route.ts @@ -0,0 +1,6 @@ +import { disableDraftMode } from "next-drupal/draft" +import type { NextRequest } from "next/server" + +export async function GET(request: NextRequest) { + return disableDraftMode() +} diff --git a/starters/basic-starter/app/api/draft/route.ts b/starters/basic-starter/app/api/draft/route.ts new file mode 100644 index 00000000..b8757e2a --- /dev/null +++ b/starters/basic-starter/app/api/draft/route.ts @@ -0,0 +1,7 @@ +import { drupal } from "@/lib/drupal" +import { enableDraftMode } from "next-drupal/draft" +import type { NextRequest } from "next/server" + +export async function GET(request: NextRequest): Promise { + return enableDraftMode(request, drupal) +} diff --git a/starters/basic-starter/app/api/revalidate/route.ts b/starters/basic-starter/app/api/revalidate/route.ts new file mode 100644 index 00000000..9882273a --- /dev/null +++ b/starters/basic-starter/app/api/revalidate/route.ts @@ -0,0 +1,28 @@ +import { revalidatePath } from "next/cache" +import type { NextRequest } from "next/server" + +async function handler(request: NextRequest) { + const searchParams = request.nextUrl.searchParams + const path = searchParams.get("path") + const secret = searchParams.get("secret") + + // Validate secret. + if (secret !== process.env.DRUPAL_REVALIDATE_SECRET) { + return new Response("Invalid secret.", { status: 401 }) + } + + // Validate path. + if (!path) { + return new Response("Invalid path.", { status: 400 }) + } + + try { + revalidatePath(path) + + return new Response("Revalidated.") + } catch (error) { + return new Response((error as Error).message, { status: 500 }) + } +} + +export { handler as GET, handler as POST } diff --git a/starters/basic-starter/app/layout.tsx b/starters/basic-starter/app/layout.tsx new file mode 100644 index 00000000..3b3c9652 --- /dev/null +++ b/starters/basic-starter/app/layout.tsx @@ -0,0 +1,37 @@ +import { DraftAlert } from "@/components/misc/DraftAlert" +import { HeaderNav } from "@/components/navigation/HeaderNav" +import type { Metadata } from "next" +import type { ReactNode } from "react" + +import "@/styles/globals.css" + +export const metadata: Metadata = { + title: { + default: "Next.js for Drupal", + template: "%s | Next.js for Drupal", + }, + description: "A Next.js site powered by a Drupal backend.", + icons: { + icon: "/favicon.ico", + }, +} + +export default function RootLayout({ + // Layouts must accept a children prop. + // This will be populated with nested layouts or pages + children, +}: { + children: ReactNode +}) { + return ( + + + +
+ +
{children}
+
+ + + ) +} diff --git a/starters/basic-starter/pages/index.tsx b/starters/basic-starter/app/page.tsx similarity index 50% rename from starters/basic-starter/pages/index.tsx rename to starters/basic-starter/app/page.tsx index e5963b98..01aaac5a 100644 --- a/starters/basic-starter/pages/index.tsx +++ b/starters/basic-starter/app/page.tsx @@ -1,14 +1,15 @@ -import Head from "next/head" import { ArticleTeaser } from "@/components/drupal/ArticleTeaser" -import { Layout } from "@/components/Layout" import { drupal } from "@/lib/drupal" -import type { InferGetStaticPropsType, GetStaticProps } from "next" +import type { Metadata } from "next" import type { DrupalNode } from "next-drupal" -export const getStaticProps = (async (context) => { - const nodes = await drupal.getResourceCollectionFromContext( +export const metadata: Metadata = { + description: "A Next.js site powered by a Drupal backend.", +} + +export default async function Home() { + const nodes = await drupal.getResourceCollection( "node--article", - context, { params: { "filter[status]": 1, @@ -19,28 +20,8 @@ export const getStaticProps = (async (context) => { } ) - return { - props: { - nodes, - }, - } -}) satisfies GetStaticProps<{ - nodes: DrupalNode[] -}> - -export default function Home({ - nodes, -}: InferGetStaticPropsType) { return ( - - - Next.js for Drupal - - + <>

Latest Articles.

{nodes?.length ? ( nodes.map((node) => ( @@ -52,6 +33,6 @@ export default function Home({ ) : (

No nodes found

)} -
+ ) } diff --git a/starters/basic-starter/components/Layout.tsx b/starters/basic-starter/components/Layout.tsx deleted file mode 100644 index cb1f4012..00000000 --- a/starters/basic-starter/components/Layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { HeaderNav } from "@/components/navigation/HeaderNav" -import { PreviewAlert } from "@/components/misc/PreviewAlert" -import type { ReactNode } from "react" - -export function Layout({ children }: { children: ReactNode }) { - return ( - <> - -
- -
{children}
-
- - ) -} diff --git a/starters/basic-starter/components/misc/PreviewAlert.tsx b/starters/basic-starter/components/misc/DraftAlert/Client.tsx similarity index 52% rename from starters/basic-starter/components/misc/PreviewAlert.tsx rename to starters/basic-starter/components/misc/DraftAlert/Client.tsx index edce5720..00932ae4 100644 --- a/starters/basic-starter/components/misc/PreviewAlert.tsx +++ b/starters/basic-starter/components/misc/DraftAlert/Client.tsx @@ -1,33 +1,36 @@ +"use client" + import { useEffect, useState } from "react" -import { useRouter } from "next/router" -export function PreviewAlert() { - const router = useRouter() - const isPreview = router.isPreview - const [showPreviewAlert, setShowPreviewAlert] = useState(false) +export function DraftAlertClient({ + isDraftEnabled, +}: { + isDraftEnabled: boolean +}) { + const [showDraftAlert, setShowDraftAlert] = useState(false) useEffect(() => { - setShowPreviewAlert(isPreview && window.top === window.self) - }, [isPreview]) + setShowDraftAlert(isDraftEnabled && window.top === window.self) + }, [isDraftEnabled]) - if (!showPreviewAlert) { + if (!showDraftAlert) { return null } function buttonHandler() { - void fetch("/api/exit-preview") - setShowPreviewAlert(false) + void fetch("/api/disable-draft") + setShowDraftAlert(false) } return (

- This page is a preview.{" "} + This page is a draft.

diff --git a/starters/basic-starter/components/misc/DraftAlert/index.tsx b/starters/basic-starter/components/misc/DraftAlert/index.tsx new file mode 100644 index 00000000..a07f0d67 --- /dev/null +++ b/starters/basic-starter/components/misc/DraftAlert/index.tsx @@ -0,0 +1,13 @@ +import { Suspense } from "react" +import { draftMode } from "next/headers" +import { DraftAlertClient } from "./Client" + +export function DraftAlert() { + const isDraftEnabled = draftMode().isEnabled + + return ( + + + + ) +} diff --git a/starters/basic-starter/lib/drupal.ts b/starters/basic-starter/lib/drupal.ts index 9b2f40c0..a779d5dd 100644 --- a/starters/basic-starter/lib/drupal.ts +++ b/starters/basic-starter/lib/drupal.ts @@ -1,14 +1,13 @@ -import { NextDrupalPages } from "next-drupal" +import { NextDrupal } from "next-drupal" const baseUrl = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL as string const clientId = process.env.DRUPAL_CLIENT_ID as string const clientSecret = process.env.DRUPAL_CLIENT_SECRET as string -export const drupal = new NextDrupalPages(baseUrl, { +export const drupal = new NextDrupal(baseUrl, { auth: { clientId, clientSecret, }, - useDefaultEndpoints: true, // debug: true, }) diff --git a/starters/basic-starter/pages/[...slug].tsx b/starters/basic-starter/pages/[...slug].tsx deleted file mode 100644 index 9894d5f0..00000000 --- a/starters/basic-starter/pages/[...slug].tsx +++ /dev/null @@ -1,92 +0,0 @@ -import Head from "next/head" -import { Article } from "@/components/drupal/Article" -import { BasicPage } from "@/components/drupal/BasicPage" -import { Layout } from "@/components/Layout" -import { drupal } from "@/lib/drupal" -import type { - GetStaticPaths, - GetStaticProps, - InferGetStaticPropsType, -} from "next" -import type { DrupalNode } from "next-drupal" - -const RESOURCE_TYPES = ["node--page", "node--article"] - -export const getStaticPaths = (async (context) => { - return { - paths: await drupal.getStaticPathsFromContext(RESOURCE_TYPES, context), - fallback: "blocking", - } -}) satisfies GetStaticPaths - -export const getStaticProps = (async (context) => { - const path = await drupal.translatePathFromContext(context) - - if (!path) { - return { - notFound: true, - } - } - - const type = path?.jsonapi?.resourceName - - let params = {} - if (type === "node--article") { - params = { - include: "field_image,uid", - } - } - - const resource = await drupal.getResourceFromContext( - path, - context, - { - params, - } - ) - - // At this point, we know the path exists and it points to a resource. - // If we receive an error, it means something went wrong on Drupal. - // We throw an error to tell revalidation to skip this for now. - // Revalidation can try again on next request. - if (!resource) { - throw new Error(`Failed to fetch resource: ${path?.jsonapi?.individual}`) - } - - // If we're not in preview mode and the resource is not published, - // Return page not found. - if (!context.preview && resource?.status === false) { - return { - notFound: true, - } - } - - return { - props: { - resource, - }, - } -}) satisfies GetStaticProps<{ - resource: DrupalNode -}> - -export default function NodePage({ - resource, -}: InferGetStaticPropsType) { - if (!resource) return null - - return ( - - - {resource.title} - - - {resource.type === "node--page" && } - {resource.type === "node--article" &&
} - - ) -} diff --git a/starters/basic-starter/pages/_app.tsx b/starters/basic-starter/pages/_app.tsx deleted file mode 100644 index 70739e9b..00000000 --- a/starters/basic-starter/pages/_app.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import "@/styles/globals.css" -import type { AppProps } from "next/app" - -export default function App({ Component, pageProps }: AppProps) { - return -} diff --git a/starters/basic-starter/pages/_document.tsx b/starters/basic-starter/pages/_document.tsx deleted file mode 100644 index 097cb7ff..00000000 --- a/starters/basic-starter/pages/_document.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Html, Head, Main, NextScript } from "next/document" - -export default function Document() { - return ( - - - -
- - - - ) -} diff --git a/starters/basic-starter/pages/api/exit-preview.ts b/starters/basic-starter/pages/api/exit-preview.ts deleted file mode 100644 index f8847b39..00000000 --- a/starters/basic-starter/pages/api/exit-preview.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { drupal } from "@/lib/drupal" -import type { NextApiRequest, NextApiResponse } from "next" - -export default async function exit( - request: NextApiRequest, - response: NextApiResponse -) { - await drupal.previewDisable(request, response) -} diff --git a/starters/basic-starter/pages/api/preview.ts b/starters/basic-starter/pages/api/preview.ts deleted file mode 100644 index a0733440..00000000 --- a/starters/basic-starter/pages/api/preview.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { drupal } from "@/lib/drupal" -import type { NextApiRequest, NextApiResponse } from "next" - -export default async function draft( - request: NextApiRequest, - response: NextApiResponse -) { - // Enables Preview mode and Draft mode. - await drupal.preview(request, response, { enable: true }) -} diff --git a/starters/basic-starter/pages/api/revalidate.ts b/starters/basic-starter/pages/api/revalidate.ts deleted file mode 100644 index 368a16c8..00000000 --- a/starters/basic-starter/pages/api/revalidate.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next" - -export default async function handler( - request: NextApiRequest, - response: NextApiResponse -) { - let path = request.query.path as string - const secret = request.query.secret as string - - // Validate secret. - if (secret !== process.env.DRUPAL_REVALIDATE_SECRET) { - return response.status(401).json({ message: "Invalid secret." }) - } - - // Validate path. - if (!path) { - return response.status(400).json({ message: "Invalid path." }) - } - - try { - await response.revalidate(path) - - return response.json({}) - } catch (error) { - return response.status(404).json({ - message: (error as Error).message, - }) - } -} diff --git a/starters/graphql-starter/pages/[...slug].tsx b/starters/graphql-starter/app/[...slug]/page.tsx similarity index 51% rename from starters/graphql-starter/pages/[...slug].tsx rename to starters/graphql-starter/app/[...slug]/page.tsx index 4ca04106..9fb1921b 100644 --- a/starters/graphql-starter/pages/[...slug].tsx +++ b/starters/graphql-starter/app/[...slug]/page.tsx @@ -1,54 +1,14 @@ -import Head from "next/head" +import { draftMode } from "next/headers" +import { notFound } from "next/navigation" +import { getDraftData } from "next-drupal/draft" import { Article } from "@/components/drupal/Article" import { BasicPage } from "@/components/drupal/BasicPage" -import { Layout } from "@/components/Layout" import { drupal } from "@/lib/drupal" -import type { - GetStaticPaths, - GetStaticProps, - InferGetStaticPropsType, -} from "next" +import type { Metadata, ResolvingMetadata } from "next" import type { DrupalArticle, DrupalPage, NodesPath } from "@/types" -export const getStaticPaths = (async (context) => { - // Fetch the paths for the first 50 articles and pages. - // We'll fall back to on-demand generation for the rest. - const data = await drupal.query<{ - nodeArticles: NodesPath - nodePages: NodesPath - }>({ - query: `query { - nodeArticles(first: 50) { - nodes { - path, - } - } - nodePages(first: 50) { - nodes { - path, - } - } - }`, - }) - - // Build static paths. - const paths = [ - ...(data?.nodeArticles?.nodes as { path: string }[]), - ...(data?.nodePages?.nodes as { path: string }[]), - ].map(({ path }) => ({ params: { slug: path.split("/").filter(Boolean) } })) - - return { - paths, - fallback: "blocking", - } -}) satisfies GetStaticPaths - -export const getStaticProps = (async (context) => { - if (!context?.params?.slug) { - return { - notFound: true, - } - } +async function getNode(slug: string[]) { + const path = `/${slug.join("/")}` const data = await drupal.query<{ route: { entity: DrupalArticle | DrupalPage } @@ -92,46 +52,96 @@ export const getStaticProps = (async (context) => { } }`, variables: { - path: `/${(context.params.slug as []).join("/")}`, + path, }, }) const resource = data?.route?.entity - // If we're not in preview mode and the resource is not published, - // Return page not found. - if (!resource || (!context.preview && resource?.status === false)) { - return { - notFound: true, - } + if (!resource) { + throw new Error(`Failed to fetch resource: ${path}`, { + cause: "DrupalError", + }) + } + + return resource +} + +type NodePageParams = { + slug: string[] +} +type NodePageProps = { + params: NodePageParams + searchParams: { [key: string]: string | string[] | undefined } +} + +export async function generateMetadata( + { params: { slug } }: NodePageProps, + parent: ResolvingMetadata +): Promise { + let node + try { + node = await getNode(slug) + } catch (e) { + // If we fail to fetch the node, don't return any metadata. + return {} } return { - props: { - resource, - }, + title: node.title, + } +} + +export async function generateStaticParams(): Promise { + // Fetch the paths for the first 50 articles and pages. + // We'll fall back to on-demand generation for the rest. + const data = await drupal.query<{ + nodeArticles: NodesPath + nodePages: NodesPath + }>({ + query: `query { + nodeArticles(first: 50) { + nodes { + path, + } + } + nodePages(first: 50) { + nodes { + path, + } + } + }`, + }) + + return [ + ...(data?.nodeArticles?.nodes as { path: string }[]), + ...(data?.nodePages?.nodes as { path: string }[]), + ].map(({ path }) => ({ slug: path.split("/").filter(Boolean) })) +} + +export default async function Page({ + params: { slug }, + searchParams, +}: NodePageProps) { + const isDraftMode = draftMode().isEnabled + + let node + try { + node = await getNode(slug) + } catch (error) { + // If getNode throws an error, tell Next.js the path is 404. + notFound() } -}) satisfies GetStaticProps<{ - resource: DrupalArticle | DrupalPage -}> -export default function Page({ - resource, -}: InferGetStaticPropsType) { - if (!resource) return null + // If we're not in draft mode and the resource is not published, return a 404. + if (!isDraftMode && node?.status === false) { + notFound() + } return ( - - - {resource.title} - - - {resource.__typename === "NodePage" && } - {resource.__typename === "NodeArticle" &&
} - + <> + {node.__typename === "NodePage" && } + {node.__typename === "NodeArticle" &&
} + ) } diff --git a/starters/graphql-starter/app/api/disable-draft/route.ts b/starters/graphql-starter/app/api/disable-draft/route.ts new file mode 100644 index 00000000..81900948 --- /dev/null +++ b/starters/graphql-starter/app/api/disable-draft/route.ts @@ -0,0 +1,6 @@ +import { disableDraftMode } from "next-drupal/draft" +import type { NextRequest } from "next/server" + +export async function GET(request: NextRequest) { + return disableDraftMode() +} diff --git a/starters/graphql-starter/app/api/draft/route.ts b/starters/graphql-starter/app/api/draft/route.ts new file mode 100644 index 00000000..b8757e2a --- /dev/null +++ b/starters/graphql-starter/app/api/draft/route.ts @@ -0,0 +1,7 @@ +import { drupal } from "@/lib/drupal" +import { enableDraftMode } from "next-drupal/draft" +import type { NextRequest } from "next/server" + +export async function GET(request: NextRequest): Promise { + return enableDraftMode(request, drupal) +} diff --git a/starters/graphql-starter/app/api/revalidate/route.ts b/starters/graphql-starter/app/api/revalidate/route.ts new file mode 100644 index 00000000..9882273a --- /dev/null +++ b/starters/graphql-starter/app/api/revalidate/route.ts @@ -0,0 +1,28 @@ +import { revalidatePath } from "next/cache" +import type { NextRequest } from "next/server" + +async function handler(request: NextRequest) { + const searchParams = request.nextUrl.searchParams + const path = searchParams.get("path") + const secret = searchParams.get("secret") + + // Validate secret. + if (secret !== process.env.DRUPAL_REVALIDATE_SECRET) { + return new Response("Invalid secret.", { status: 401 }) + } + + // Validate path. + if (!path) { + return new Response("Invalid path.", { status: 400 }) + } + + try { + revalidatePath(path) + + return new Response("Revalidated.") + } catch (error) { + return new Response((error as Error).message, { status: 500 }) + } +} + +export { handler as GET, handler as POST } diff --git a/starters/graphql-starter/app/layout.tsx b/starters/graphql-starter/app/layout.tsx new file mode 100644 index 00000000..3b3c9652 --- /dev/null +++ b/starters/graphql-starter/app/layout.tsx @@ -0,0 +1,37 @@ +import { DraftAlert } from "@/components/misc/DraftAlert" +import { HeaderNav } from "@/components/navigation/HeaderNav" +import type { Metadata } from "next" +import type { ReactNode } from "react" + +import "@/styles/globals.css" + +export const metadata: Metadata = { + title: { + default: "Next.js for Drupal", + template: "%s | Next.js for Drupal", + }, + description: "A Next.js site powered by a Drupal backend.", + icons: { + icon: "/favicon.ico", + }, +} + +export default function RootLayout({ + // Layouts must accept a children prop. + // This will be populated with nested layouts or pages + children, +}: { + children: ReactNode +}) { + return ( + + + +
+ +
{children}
+
+ + + ) +} diff --git a/starters/graphql-starter/pages/index.tsx b/starters/graphql-starter/app/page.tsx similarity index 61% rename from starters/graphql-starter/pages/index.tsx rename to starters/graphql-starter/app/page.tsx index 14544955..1ea902b9 100644 --- a/starters/graphql-starter/pages/index.tsx +++ b/starters/graphql-starter/app/page.tsx @@ -1,11 +1,13 @@ -import Head from "next/head" import { ArticleTeaser } from "@/components/drupal/ArticleTeaser" -import { Layout } from "@/components/Layout" import { drupal } from "@/lib/drupal" -import type { InferGetStaticPropsType, GetStaticProps } from "next" +import type { Metadata } from "next" import type { DrupalArticle } from "@/types" -export const getStaticProps = (async (context) => { +export const metadata: Metadata = { + description: "A Next.js site powered by a Drupal backend.", +} + +export default async function Home() { // Fetch the first 10 articles. const data = await drupal.query<{ nodeArticles: { @@ -38,29 +40,10 @@ export const getStaticProps = (async (context) => { } `, }) + const nodes = data?.nodeArticles?.nodes ?? [] - return { - props: { - nodes: data?.nodeArticles?.nodes ?? [], - }, - } -}) satisfies GetStaticProps<{ - nodes: DrupalArticle[] -}> - -export default function Home({ - nodes, -}: InferGetStaticPropsType) { return ( - - - Next.js for Drupal - - + <>

Latest Articles.

{nodes?.length ? ( nodes.map((node) => ( @@ -72,6 +55,6 @@ export default function Home({ ) : (

No nodes found

)} -
+ ) } diff --git a/starters/graphql-starter/components/Layout.tsx b/starters/graphql-starter/components/Layout.tsx deleted file mode 100644 index cb1f4012..00000000 --- a/starters/graphql-starter/components/Layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { HeaderNav } from "@/components/navigation/HeaderNav" -import { PreviewAlert } from "@/components/misc/PreviewAlert" -import type { ReactNode } from "react" - -export function Layout({ children }: { children: ReactNode }) { - return ( - <> - -
- -
{children}
-
- - ) -} diff --git a/starters/graphql-starter/components/misc/PreviewAlert.tsx b/starters/graphql-starter/components/misc/DraftAlert/Client.tsx similarity index 52% rename from starters/graphql-starter/components/misc/PreviewAlert.tsx rename to starters/graphql-starter/components/misc/DraftAlert/Client.tsx index edce5720..00932ae4 100644 --- a/starters/graphql-starter/components/misc/PreviewAlert.tsx +++ b/starters/graphql-starter/components/misc/DraftAlert/Client.tsx @@ -1,33 +1,36 @@ +"use client" + import { useEffect, useState } from "react" -import { useRouter } from "next/router" -export function PreviewAlert() { - const router = useRouter() - const isPreview = router.isPreview - const [showPreviewAlert, setShowPreviewAlert] = useState(false) +export function DraftAlertClient({ + isDraftEnabled, +}: { + isDraftEnabled: boolean +}) { + const [showDraftAlert, setShowDraftAlert] = useState(false) useEffect(() => { - setShowPreviewAlert(isPreview && window.top === window.self) - }, [isPreview]) + setShowDraftAlert(isDraftEnabled && window.top === window.self) + }, [isDraftEnabled]) - if (!showPreviewAlert) { + if (!showDraftAlert) { return null } function buttonHandler() { - void fetch("/api/exit-preview") - setShowPreviewAlert(false) + void fetch("/api/disable-draft") + setShowDraftAlert(false) } return (

- This page is a preview.{" "} + This page is a draft.

diff --git a/starters/graphql-starter/components/misc/DraftAlert/index.tsx b/starters/graphql-starter/components/misc/DraftAlert/index.tsx new file mode 100644 index 00000000..a07f0d67 --- /dev/null +++ b/starters/graphql-starter/components/misc/DraftAlert/index.tsx @@ -0,0 +1,13 @@ +import { Suspense } from "react" +import { draftMode } from "next/headers" +import { DraftAlertClient } from "./Client" + +export function DraftAlert() { + const isDraftEnabled = draftMode().isEnabled + + return ( + + + + ) +} diff --git a/starters/graphql-starter/pages/_app.tsx b/starters/graphql-starter/pages/_app.tsx deleted file mode 100644 index 70739e9b..00000000 --- a/starters/graphql-starter/pages/_app.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import "@/styles/globals.css" -import type { AppProps } from "next/app" - -export default function App({ Component, pageProps }: AppProps) { - return -} diff --git a/starters/graphql-starter/pages/_document.tsx b/starters/graphql-starter/pages/_document.tsx deleted file mode 100644 index 097cb7ff..00000000 --- a/starters/graphql-starter/pages/_document.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Html, Head, Main, NextScript } from "next/document" - -export default function Document() { - return ( - - - -
- - - - ) -} diff --git a/starters/graphql-starter/pages/api/exit-preview.ts b/starters/graphql-starter/pages/api/exit-preview.ts deleted file mode 100644 index f8847b39..00000000 --- a/starters/graphql-starter/pages/api/exit-preview.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { drupal } from "@/lib/drupal" -import type { NextApiRequest, NextApiResponse } from "next" - -export default async function exit( - request: NextApiRequest, - response: NextApiResponse -) { - await drupal.previewDisable(request, response) -} diff --git a/starters/graphql-starter/pages/api/preview.ts b/starters/graphql-starter/pages/api/preview.ts deleted file mode 100644 index a0733440..00000000 --- a/starters/graphql-starter/pages/api/preview.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { drupal } from "@/lib/drupal" -import type { NextApiRequest, NextApiResponse } from "next" - -export default async function draft( - request: NextApiRequest, - response: NextApiResponse -) { - // Enables Preview mode and Draft mode. - await drupal.preview(request, response, { enable: true }) -} diff --git a/starters/graphql-starter/pages/api/revalidate.ts b/starters/graphql-starter/pages/api/revalidate.ts deleted file mode 100644 index 368a16c8..00000000 --- a/starters/graphql-starter/pages/api/revalidate.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next" - -export default async function handler( - request: NextApiRequest, - response: NextApiResponse -) { - let path = request.query.path as string - const secret = request.query.secret as string - - // Validate secret. - if (secret !== process.env.DRUPAL_REVALIDATE_SECRET) { - return response.status(401).json({ message: "Invalid secret." }) - } - - // Validate path. - if (!path) { - return response.status(400).json({ message: "Invalid path." }) - } - - try { - await response.revalidate(path) - - return response.json({}) - } catch (error) { - return response.status(404).json({ - message: (error as Error).message, - }) - } -} From 1815ac04a1c055a051e8ad58a541aec485054f21 Mon Sep 17 00:00:00 2001 From: JohnAlbin Date: Tue, 23 Apr 2024 01:58:01 +0800 Subject: [PATCH 7/7] fix(next-drupal): use NextDrupalBase in enableDraftMode() Fixes #749 --- packages/next-drupal/src/draft.ts | 4 ++-- packages/next-drupal/tests/draft/draft.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/next-drupal/src/draft.ts b/packages/next-drupal/src/draft.ts index 39da1396..c47eba42 100644 --- a/packages/next-drupal/src/draft.ts +++ b/packages/next-drupal/src/draft.ts @@ -5,11 +5,11 @@ import { DRAFT_MODE_COOKIE_NAME, } from "./draft-constants" import type { NextRequest } from "next/server" -import type { NextDrupal } from "./next-drupal" +import type { NextDrupalBase } from "./next-drupal-base" export async function enableDraftMode( request: NextRequest, - drupal: NextDrupal + drupal: NextDrupalBase ): Promise { // Validate the draft request. const response = await drupal.validateDraftUrl(request.nextUrl.searchParams) diff --git a/packages/next-drupal/tests/draft/draft.test.ts b/packages/next-drupal/tests/draft/draft.test.ts index 61ab9d51..22296f40 100644 --- a/packages/next-drupal/tests/draft/draft.test.ts +++ b/packages/next-drupal/tests/draft/draft.test.ts @@ -12,7 +12,7 @@ import { NextRequest } from "next/server" import { DRAFT_DATA_COOKIE_NAME, DRAFT_MODE_COOKIE_NAME, - NextDrupal, + NextDrupalBase, } from "../../src" import { BASE_URL, spyOnFetch } from "../utils" import { @@ -50,7 +50,7 @@ describe("enableDraftMode()", () => { const request = new NextRequest( `https://example.com/api/draft?${searchParams}` ) - const drupal = new NextDrupal(BASE_URL) + const drupal = new NextDrupalBase(BASE_URL) const draftModeCookie: ResponseCookie = { name: DRAFT_MODE_COOKIE_NAME, value: "some-secret-key",