Skip to content

Commit 5823d45

Browse files
Merge branch 'canary' into shu/5dyt
2 parents 5dc9f37 + d735d31 commit 5823d45

29 files changed

+324
-510
lines changed

errors/large-page-data.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Reduce the amount of data returned from `getStaticProps`, `getServerSideProps`,
1313
To inspect the props passed to your page, you can inspect the below element's content in your browser devtools:
1414

1515
```bash filename="Terminal"
16-
document.getElementById("__NEXT_DATA__").text
16+
JSON.parse(document.getElementById("__NEXT_DATA__").textContent)
1717
```
1818

1919
## Useful Links

examples/with-fauna/README.md

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
# Fauna GraphQL Guestbook Starter
1+
# Fauna Guestbook Starter
22

3-
This Guestbook Single-Page Application (SPA) example shows you how to use [Fauna's GraphQL endpoint](https://docs.fauna.com/fauna/current/api/graphql/) in your Next.js project.
3+
This Guestbook Application example shows you how to use [Fauna](https://docs.fauna.com/) in your Next.js project.
44

55
## Deploy your own
66

77
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
88

99
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-fauna&project-name=fauna-nextjs-guestbook&repository-name=fauna-nextjs-guestbook&demo-title=Next.js%20Fauna%20Guestbook%20App&demo-description=A%20simple%20guestbook%20application%20built%20with%20Next.js%20and%20Fauna&integration-ids=oac_Erlbqm8Teb1y4WhioE3r2utY)
1010

11-
## Why Fauna
12-
13-
By importing a `.gql` or `.graphql` schema into Fauna ([see our sample schema file](./schema.gql)), Fauna will generate required Indexes and GraphQL resolvers for you -- hands free 👐 ([some limitations exist](https://docs.fauna.com/fauna/current/api/graphql/#limitations)).
14-
1511
## How to use
1612

1713
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:
@@ -26,17 +22,12 @@ pnpm create next-app --example with-fauna with-fauna-app
2622

2723
You can start with this template [using `create-next-app`](#using-create-next-app) or by [downloading the repository manually](#download-manually).
2824

29-
To use a live Fauna database, create a database at [dashboard.fauna.com](https://dashboard.fauna.com/) and generate an admin token by going to the **Security** tab on the left and then click **New Key**. Give the new key a name and select the 'Admin' Role. Copy the token since the setup script will ask for it. Do not use it in the frontend, it has superpowers which you don't want to give to your users.
30-
31-
### Setting Up Your Schema
32-
33-
The Next.js and Fauna example includes a setup script (`npm run setup`). After providing your admin token, the script will:
25+
### Setting Up Your Fauna Database
3426

35-
- **Import your GraphQL schema:** Fauna automatically sets up collections and indexes to support your queries. You can view these in your [project dashboard](https://dashboard.fauna.com/) under **GraphQL**.
36-
- **Create an index and function:** The script will create a GraphQL resolver that uses [User-defined functions](https://docs.fauna.com/fauna/current/api/graphql/functions?lang=javascript) based on a sorting index.
37-
- **Create a scoped token:** This token is for use on the client side. The admin key can be used on the server side.
27+
Head over to [Fauna Dashboard](https://dashboard.fauna.com/) and create a new database. You can name it whatever you want, but for this example, we'll use `nextjs-guestbook`. Next, create a new collection called `Entry` in your new database.
28+
Finally create a new database access key to connect to your database.
3829

39-
After the script completes, a `.env.local` [file](https://nextjs.org/docs/basic-features/environment-variables) will be created for you with the newly generated client token assigned to an Environment Variable.
30+
Watch [this video](https://www.youtube.com/watch?v=8YJcG2fUPyE&t=43s&ab_channel=FaunaInc.) to learn how to connect to your database.
4031

4132
### Run locally
4233

examples/with-fauna/actions/entry.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use server'
2+
3+
import { revalidatePath } from 'next/cache'
4+
import { createEntry } from '@/lib/fauna'
5+
6+
export async function createEntryAction(prevState: any, formData: FormData) {
7+
const name = formData.get('name') as string
8+
const message = formData.get('message') as string
9+
try {
10+
await createEntry(name, message)
11+
revalidatePath('/')
12+
return {
13+
successMessage: 'Thank you for signing the guest book',
14+
errorMessage: null,
15+
}
16+
} catch (error) {
17+
return {
18+
successMessage: null,
19+
errorMessage: 'Something went wrong. Please try again',
20+
}
21+
}
22+
}

examples/with-fauna/app/globals.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import cn from 'classnames'
2+
import formatDate from 'date-fns/format'
3+
import EntryForm from '@/components/EntryForm'
4+
import { EntryType } from './page'
5+
6+
const EntryItem = ({ entry }: { entry: EntryType }) => (
7+
<div className="flex flex-col space-y-2">
8+
<div className="prose dark:prose-dark w-full">{entry.message}</div>
9+
<div className="flex items-center space-x-3">
10+
<p className="text-sm text-gray-500">{entry.name}</p>
11+
<span className="text-gray-200 dark:text-gray-800">/</span>
12+
<p className="text-sm text-gray-400 dark:text-gray-600">
13+
{formatDate(
14+
new Date(entry.createdAt.isoString),
15+
"d MMM yyyy 'at' h:mm bb"
16+
)}
17+
</p>
18+
</div>
19+
</div>
20+
)
21+
22+
export default async function GuestbookPage({
23+
entries,
24+
}: {
25+
entries: EntryType[]
26+
}) {
27+
return (
28+
<main className="max-w-4xl mx-auto p-4">
29+
<div
30+
className={cn(
31+
'border border-blue-200 rounded p-6',
32+
'my-4 w-full dark:border-gray-800 bg-blue-50',
33+
'dark:bg-blue-opaque'
34+
)}
35+
>
36+
<h5 className={cn('text-lg md:text-xl font-bold', 'text-gray-900')}>
37+
Sign the Guestbook
38+
</h5>
39+
<p className="my-1 text-gray-800">
40+
Share a message for a future visitor.
41+
</p>
42+
<EntryForm />
43+
</div>
44+
45+
<div className="mt-4 space-y-8 px-2">
46+
{entries?.map((entry) => (
47+
<EntryItem key={entry.id} entry={entry} />
48+
))}
49+
</div>
50+
</main>
51+
)
52+
}

examples/with-fauna/app/layout.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import './globals.css'
2+
3+
export const metadata: {
4+
title: string
5+
description: string
6+
} = {
7+
title: 'Next.js + Fauna example',
8+
description: 'Generated by Next.js',
9+
}
10+
11+
export default function RootLayout({
12+
children,
13+
}: {
14+
children: React.ReactNode
15+
}) {
16+
return (
17+
<html lang="en">
18+
<body>{children}</body>
19+
</html>
20+
)
21+
}

examples/with-fauna/app/page.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { getAllEntries } from '@/lib/fauna'
2+
import GuestbookPage from './guestbook-page'
3+
4+
export type EntryType = {
5+
id: string
6+
name: string
7+
message: string
8+
createdAt: {
9+
isoString: string
10+
}
11+
}
12+
13+
export default async function Page() {
14+
const entries = (await getAllEntries()) as EntryType[]
15+
return <GuestbookPage entries={entries} />
16+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use client'
2+
3+
import cn from 'classnames'
4+
import { createEntryAction } from '@/actions/entry'
5+
// @ts-ignore
6+
import { experimental_useFormState as useFormState } from 'react-dom'
7+
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
8+
import LoadingSpinner from '@/components/LoadingSpinner'
9+
import SuccessMessage from '@/components/SuccessMessage'
10+
import ErrorMessage from '@/components/ErrorMessage'
11+
12+
const inputClasses = cn(
13+
'block py-2 bg-white dark:bg-gray-800',
14+
'rounded-md border-gray-300 focus:ring-blue-500',
15+
'focus:border-blue-500 text-gray-900 dark:text-gray-100'
16+
)
17+
18+
const initialState = {
19+
successMessage: null,
20+
errorMessage: null,
21+
}
22+
23+
export default function EntryForm() {
24+
const [state, formAction] = useFormState(createEntryAction, initialState)
25+
const { pending } = useFormStatus()
26+
27+
return (
28+
<>
29+
<form className="flex relative my-4" action={formAction}>
30+
<input
31+
required
32+
className={cn(inputClasses, 'w-1/3 mr-2 px-4')}
33+
aria-label="Your name"
34+
placeholder="Your name..."
35+
name="name"
36+
/>
37+
<input
38+
required
39+
className={cn(inputClasses, 'pl-4 pr-32 flex-grow')}
40+
aria-label="Your message"
41+
placeholder="Your message..."
42+
name="message"
43+
/>
44+
<button
45+
className={cn(
46+
'flex items-center justify-center',
47+
'absolute right-1 top-1 px-4 font-bold h-8',
48+
'bg-gray-100 dark:bg-gray-700 text-gray-900',
49+
'dark:text-gray-100 rounded w-28'
50+
)}
51+
type="submit"
52+
disabled={pending}
53+
>
54+
{pending ? <LoadingSpinner /> : 'Sign'}
55+
</button>
56+
</form>
57+
{state?.successMessage ? (
58+
<SuccessMessage>{state.successMessage}</SuccessMessage>
59+
) : null}
60+
{state?.errorMessage ? (
61+
<ErrorMessage>{state.errorMessage}</ErrorMessage>
62+
) : null}
63+
</>
64+
)
65+
}

examples/with-fauna/components/ErrorMessage.js renamed to examples/with-fauna/components/ErrorMessage.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
export default function ErrorMessage({ children }) {
1+
export default function ErrorMessage({
2+
children,
3+
}: {
4+
children: React.ReactNode
5+
}) {
26
return (
37
<p className="flex items-center text-sm font-bold text-red-800 dark:text-red-400">
48
<svg

examples/with-fauna/components/SuccessMessage.js renamed to examples/with-fauna/components/SuccessMessage.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
export default function SuccessMessage({ children }) {
1+
export default function SuccessMessage({
2+
children,
3+
}: {
4+
children: React.ReactNode
5+
}) {
26
return (
37
<p className="flex items-center text-sm font-bold text-green-700 dark:text-green-400">
48
<svg

examples/with-fauna/jsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"compilerOptions": {
33
"baseUrl": ".",
44
"paths": {
5+
"@/actions/*": ["actions/*"],
56
"@/components/*": ["components/*"],
67
"@/lib/*": ["lib/*"]
78
}

examples/with-fauna/lib/constants.js

Lines changed: 0 additions & 7 deletions
This file was deleted.

examples/with-fauna/lib/fauna.js

Lines changed: 0 additions & 49 deletions
This file was deleted.

examples/with-fauna/lib/fauna.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import 'server-only'
2+
import { Client, fql, QuerySuccess, QueryValueObject } from 'fauna'
3+
4+
const client = new Client({
5+
secret: process.env.FAUNA_CLIENT_SECRET,
6+
})
7+
8+
export const getAllEntries = async () => {
9+
try {
10+
const dbresponse: QuerySuccess<QueryValueObject> = await client.query(fql`
11+
Entry.all()
12+
`)
13+
return dbresponse.data.data
14+
} catch (error: any) {
15+
throw new Error(error.message)
16+
}
17+
}
18+
19+
export const createEntry = async (name: string, message: string) => {
20+
try {
21+
const dbresponse = await client.query(fql`
22+
Entry.create({
23+
name: ${name},
24+
message: ${message},
25+
createdAt: Time.now(),
26+
})`)
27+
return dbresponse.data
28+
} catch (error: any) {
29+
throw new Error(error.message)
30+
}
31+
}

examples/with-fauna/next.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const nextConfig = {
2+
experimental: {
3+
serverActions: true,
4+
},
5+
}
6+
7+
module.exports = nextConfig

examples/with-fauna/package.json

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,17 @@
99
"dependencies": {
1010
"classnames": "2.3.1",
1111
"date-fns": "2.28.0",
12-
"faunadb": "4.5.4",
13-
"graphql": "16.8.1",
14-
"graphql-request": "4.3.0",
12+
"fauna": "^1.2.0",
1513
"next": "latest",
16-
"react": "18.1.0",
17-
"react-dom": "18.1.0",
18-
"swr": "^2.0.0"
14+
"react": "^18.2.0",
15+
"react-dom": "^18.2.0",
16+
"server-only": "^0.0.1"
1917
},
2018
"devDependencies": {
21-
"autoprefixer": "^10.4.7",
22-
"postcss": "^8.4.14",
19+
"autoprefixer": "^10.4.16",
20+
"postcss": "^8.4.31",
21+
"request": "^2.88.2",
2322
"stream-to-promise": "3.0.0",
24-
"tailwindcss": "^3.1.2",
25-
"request": "^2.88.2"
23+
"tailwindcss": "^3.3.3"
2624
}
2725
}

0 commit comments

Comments
 (0)