Skip to content

Commit dbea732

Browse files
committed
feat: add graphql-starter
0 parents  commit dbea732

28 files changed

+2873
-0
lines changed

.env.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# See https://next-drupal.org/docs/environment-variables
2+
NEXT_PUBLIC_DRUPAL_BASE_URL=https://dev.next-drupal.org
3+
NEXT_IMAGE_DOMAIN=dev.next-drupal.org
4+
5+
# Authentication
6+
DRUPAL_CLIENT_ID=
7+
DRUPAL_CLIENT_SECRET=
8+
9+
# Required for Preview Mode
10+
DRUPAL_PREVIEW_SECRET=secret

.eslintrc.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "next",
3+
"root": true
4+
}

.gitignore

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
.next
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
lerna-debug.log*
28+
29+
# local env files
30+
.env.local
31+
.env.development.local
32+
.env.test.local
33+
.env.production.local
34+
35+
# vercel
36+
.vercel
37+
38+
/certificates/*

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# GraphQL Starter
2+
3+
A next-drupal starter for building your site with Next.js and GraphQL.
4+
5+
## How to use
6+
7+
`npx create-next-app -e https://github.com/chapter-three next-drupal-graphql-starter`
8+
9+
## Documentation
10+
11+
See https://next-drupal.org

components/layout.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Link from "next/link"
2+
3+
import { PreviewAlert } from "components/preview-alert"
4+
5+
export function Layout({ children }) {
6+
return (
7+
<>
8+
<PreviewAlert />
9+
<div className="max-w-screen-md px-6 mx-auto">
10+
<header>
11+
<div className="container flex items-center justify-between py-6 mx-auto">
12+
<Link href="/" passHref>
13+
<a className="text-2xl font-semibold no-underline">
14+
Next.js for Drupal
15+
</a>
16+
</Link>
17+
<Link href="https://next-drupal.org/docs" passHref>
18+
<a target="_blank" rel="external" className="hover:text-blue-600">
19+
Read the docs
20+
</a>
21+
</Link>
22+
</div>
23+
</header>
24+
<main className="container py-10 mx-auto">{children}</main>
25+
</div>
26+
</>
27+
)
28+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import Image from "next/image"
2+
import Link from "next/link"
3+
4+
import { absoluteUrl, formatDate } from "lib/utils"
5+
import { Article } from "types"
6+
7+
interface NodeArticleTeaserProps {
8+
node: Partial<Article>
9+
}
10+
11+
export function NodeArticleTeaser({ node, ...props }: NodeArticleTeaserProps) {
12+
return (
13+
<article {...props}>
14+
<Link href={node.path} passHref>
15+
<a className="no-underline hover:text-blue-600">
16+
<h2 className="mb-4 text-4xl font-bold">{node.title}</h2>
17+
</a>
18+
</Link>
19+
<div className="mb-4 text-gray-600">
20+
{node.author?.displayName ? (
21+
<span>
22+
Posted by{" "}
23+
<span className="font-semibold">{node.author.displayName}</span>
24+
</span>
25+
) : null}
26+
<span> - {formatDate(node.created)}</span>
27+
</div>
28+
{node.image && (
29+
<figure className="my-4">
30+
<Image
31+
src={node.image.url}
32+
width={768}
33+
height={480}
34+
layout="responsive"
35+
objectFit="cover"
36+
alt={node.title}
37+
/>
38+
</figure>
39+
)}
40+
<Link href={node.path} passHref>
41+
<a className="inline-flex items-center px-6 py-2 border border-gray-600 rounded-full hover:bg-gray-100">
42+
Read article
43+
<svg
44+
viewBox="0 0 24 24"
45+
fill="none"
46+
stroke="currentColor"
47+
strokeWidth="2"
48+
strokeLinecap="round"
49+
strokeLinejoin="round"
50+
className="w-4 h-4 ml-2"
51+
>
52+
<path d="M5 12h14M12 5l7 7-7 7" />
53+
</svg>
54+
</a>
55+
</Link>
56+
</article>
57+
)
58+
}

components/node--article.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import Image from "next/image"
2+
3+
import { formatDate } from "lib/utils"
4+
import { Article } from "types"
5+
6+
interface NodeArticleProps {
7+
node: Article
8+
}
9+
10+
export function NodeArticle({ node, ...props }: NodeArticleProps) {
11+
return (
12+
<article {...props}>
13+
<h1 className="mb-4 text-6xl font-black leading-tight">{node.title}</h1>
14+
<div className="mb-4 text-gray-600">
15+
{node.author?.displayName ? (
16+
<span>
17+
Posted by{" "}
18+
<span className="font-semibold">{node.author.displayName}</span>
19+
</span>
20+
) : null}
21+
<span> - {formatDate(node.created)}</span>
22+
</div>
23+
{node.image && (
24+
<figure className="my-4">
25+
<Image
26+
src={node.image.url}
27+
width={768}
28+
height={480}
29+
layout="responsive"
30+
objectFit="cover"
31+
alt={node.title}
32+
/>
33+
</figure>
34+
)}
35+
{node.body?.processed && (
36+
<div
37+
dangerouslySetInnerHTML={{ __html: node.body.processed }}
38+
className="mt-6 font-serif text-xl leading-loose prose"
39+
/>
40+
)}
41+
</article>
42+
)
43+
}

components/node--basic-page.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Page } from "types"
2+
3+
interface NodeBasicPageProps {
4+
node: Page
5+
}
6+
7+
export function NodeBasicPage({ node, ...props }: NodeBasicPageProps) {
8+
return (
9+
<article {...props}>
10+
<h1 className="mb-4 text-6xl font-black leading-tight">{node.title}</h1>
11+
{node.body?.processed && (
12+
<div
13+
dangerouslySetInnerHTML={{ __html: node.body?.processed }}
14+
className="mt-6 font-serif text-xl leading-loose prose"
15+
/>
16+
)}
17+
</article>
18+
)
19+
}

components/preview-alert.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as React from "react"
2+
import { useRouter } from "next/router"
3+
4+
export function PreviewAlert() {
5+
const { isPreview } = useRouter()
6+
const [showPreviewAlert, setShowPreviewAlert] = React.useState<boolean>(false)
7+
8+
React.useEffect(() => {
9+
setShowPreviewAlert(isPreview && window.top === window.self)
10+
}, [isPreview])
11+
12+
if (!showPreviewAlert) {
13+
return null
14+
}
15+
16+
return (
17+
<div className="sticky top-0 left-0 z-50 w-full px-2 py-1 text-center text-white bg-black">
18+
<p className="mb-0">
19+
This page is a preview.{" "}
20+
{/* eslint-disable @next/next/no-html-link-for-pages */}
21+
<a href="/api/exit-preview" className="text-white underline">
22+
Click here
23+
</a>{" "}
24+
to exit preview mode.
25+
</p>
26+
</div>
27+
)
28+
}

lib/drupal.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { DrupalClient } from "next-drupal"
2+
3+
export const drupal = new DrupalClient(
4+
process.env.NEXT_PUBLIC_DRUPAL_BASE_URL,
5+
{
6+
previewSecret: process.env.DRUPAL_PREVIEW_SECRET,
7+
auth: {
8+
clientId: process.env.DRUPAL_CLIENT_ID,
9+
clientSecret: process.env.DRUPAL_CLIENT_SECRET,
10+
},
11+
}
12+
)
13+
14+
export const graphqlEndpoint = drupal.buildUrl("/graphql")
15+
16+
type QueryPayload = {
17+
query: string
18+
variables?: Record<string, string>
19+
}
20+
21+
type QueryJsonResponse<DataType> = {
22+
data?: DataType
23+
errors?: { message: string }[]
24+
}
25+
26+
// This is a wrapper around drupal.fetch.
27+
// Acts as a query helper.
28+
export async function query<DataType>(payload: QueryPayload) {
29+
const response = await drupal.fetch(graphqlEndpoint.toString(), {
30+
method: "POST",
31+
body: JSON.stringify(payload),
32+
withAuth: true, // Make authenticated requests using OAuth.
33+
})
34+
35+
if (!response?.ok) {
36+
throw new Error(response.statusText)
37+
}
38+
39+
const { data, errors }: QueryJsonResponse<DataType> = await response.json()
40+
41+
if (errors) {
42+
console.log(errors)
43+
throw new Error(errors?.map((e) => e.message).join("\n") ?? "unknown")
44+
}
45+
46+
return data
47+
}

0 commit comments

Comments
 (0)