Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade starters to App Router #720

Merged
merged 7 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ body:
- next-drupal (NPM package)
- basic-starter
- graphql-starter
- pages-starter
- example-auth
- example-blog
- example-client
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ body:
- next-drupal (NPM package)
- basic-starter
- graphql-starter
- pages-starter
- example-auth
- example-blog
- example-client
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/question.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ body:
- next-drupal (NPM package)
- basic-starter
- graphql-starter
- pages-starter
- example-auth
- example-blog
- example-client
Expand Down
1 change: 1 addition & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: #
Expand Down
1 change: 1 addition & 0 deletions MAINTAINING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
6 changes: 3 additions & 3 deletions examples/example-router-migration/lib/drupal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
final class EntityActionEventDispatcher implements DestructableInterface {

/**
* The events to dispach.
* The events to dispatch.
*
* @var \Drupal\next\Event\EntityActionEvent[]
*/
Expand All @@ -23,8 +23,8 @@ final class EntityActionEventDispatcher implements DestructableInterface {
* EntityActionEventDispatcher constructor.
*/
public function __construct(
private EventDispatcherInterface $eventDispatcher
) {
private EventDispatcherInterface $eventDispatcher,
) {
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/next-drupal/src/draft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Response | never> {
// Validate the draft request.
const response = await drupal.validateDraftUrl(request.nextUrl.searchParams)
Expand Down
4 changes: 2 additions & 2 deletions packages/next-drupal/tests/draft/draft.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions packages/next-drupal/tests/utils/mocks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from "./data"
export * from "./fetch"
export * from "./logger"
export * from "./spyOnFetch"
export * from "./mockLogger"
6 changes: 3 additions & 3 deletions packages/next-drupal/tests/utils/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 1 addition & 2 deletions starters/basic-starter/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
.pnp.js
.yarn/install-state.gz

# build/test artifacts
/.turbo
# testing
/coverage

# next.js
Expand Down
129 changes: 129 additions & 0 deletions starters/basic-starter/app/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -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<DrupalNode>(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<Metadata> {
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<NodePageParams[]> {
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" && <BasicPage node={node} />}
{node.type === "node--article" && <Article node={node} />}
</>
)
}
6 changes: 6 additions & 0 deletions starters/basic-starter/app/api/disable-draft/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { disableDraftMode } from "next-drupal/draft"
import type { NextRequest } from "next/server"

export async function GET(request: NextRequest) {
return disableDraftMode()
}
7 changes: 7 additions & 0 deletions starters/basic-starter/app/api/draft/route.ts
Original file line number Diff line number Diff line change
@@ -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<Response | never> {
return enableDraftMode(request, drupal)
}
28 changes: 28 additions & 0 deletions starters/basic-starter/app/api/revalidate/route.ts
Original file line number Diff line number Diff line change
@@ -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 }
37 changes: 37 additions & 0 deletions starters/basic-starter/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<html lang="en">
<body>
<DraftAlert />
<div className="max-w-screen-md px-6 mx-auto">
<HeaderNav />
<main className="container py-10 mx-auto">{children}</main>
</div>
</body>
</html>
)
}
38 changes: 38 additions & 0 deletions starters/basic-starter/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ArticleTeaser } from "@/components/drupal/ArticleTeaser"
import { drupal } from "@/lib/drupal"
import type { Metadata } from "next"
import type { DrupalNode } from "next-drupal"

export const metadata: Metadata = {
description: "A Next.js site powered by a Drupal backend.",
}

export default async function Home() {
const nodes = await drupal.getResourceCollection<DrupalNode[]>(
"node--article",
{
params: {
"filter[status]": 1,
"fields[node--article]": "title,path,field_image,uid,created",
include: "field_image,uid",
sort: "-created",
},
}
)

return (
<>
<h1 className="mb-10 text-6xl font-black">Latest Articles.</h1>
{nodes?.length ? (
nodes.map((node) => (
<div key={node.id}>
<ArticleTeaser node={node} />
<hr className="my-20" />
</div>
))
) : (
<p className="py-4">No nodes found</p>
)}
</>
)
}
Loading
Loading