rsnext/examples/with-fauna/pages/index.js
Adam Sobotka 31c8b97a45
Update with-faunadb dependencies (#37650)
This is just a bump of tech stack used in the example and small rewrite of swr functionality.

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm lint`
- [ x ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples)


Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
2022-06-13 02:57:56 +00:00

181 lines
4.9 KiB
JavaScript

import Head from 'next/head'
import { useState } from 'react'
import cn from 'classnames'
import formatDate from 'date-fns/format'
import useSWR, { mutate, SWRConfig } from 'swr'
import 'tailwindcss/tailwind.css'
import { listGuestbookEntries } from '@/lib/fauna'
import SuccessMessage from '@/components/SuccessMessage'
import ErrorMessage from '@/components/ErrorMessage'
import LoadingSpinner from '@/components/LoadingSpinner'
const fetcher = (url) => fetch(url).then((res) => res.json())
const putEntry = (payload) =>
fetch('/api/entries', {
method: 'POST',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json',
},
}).then((res) => (res.ok ? res.json() : Promise.reject(res)))
const useEntriesFlow = ({ fallback }) => {
const { data: entries } = useSWR('/api/entries', fetcher, {
fallbackData: fallback.entries,
})
const onSubmit = async (payload) => {
await putEntry(payload)
await mutate('/api/entries')
}
return {
entries,
onSubmit,
}
}
const AppHead = () => (
<Head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="shortcut icon" type="image/x-icon" href="/static/favicon.png" />
</Head>
)
const EntryItem = ({ entry }) => (
<div className="flex flex-col space-y-2">
<div className="prose dark:prose-dark w-full">{entry.message}</div>
<div className="flex items-center space-x-3">
<p className="text-sm text-gray-500">{entry.name}</p>
<span className="text-gray-200 dark:text-gray-800">/</span>
<p className="text-sm text-gray-400 dark:text-gray-600">
{formatDate(new Date(entry.createdAt), "d MMM yyyy 'at' h:mm bb")}
</p>
</div>
</div>
)
const EntryForm = ({ onSubmit: onSubmitProp }) => {
const initial = {
name: '',
message: '',
}
const [values, setValues] = useState(initial)
const [formState, setFormState] = useState('initial')
const isSubmitting = formState === 'submitting'
const onSubmit = (ev) => {
ev.preventDefault()
setFormState('submitting')
onSubmitProp(values)
.then(() => {
setValues(initial)
setFormState('submitted')
})
.catch(() => {
setFormState('failed')
})
}
const makeOnChange =
(fieldName) =>
({ target: { value } }) =>
setValues({
...values,
[fieldName]: value,
})
const inputClasses = cn(
'block py-2 bg-white dark:bg-gray-800',
'rounded-md border-gray-300 focus:ring-blue-500',
'focus:border-blue-500 text-gray-900 dark:text-gray-100'
)
return (
<>
<form className="flex relative my-4" onSubmit={onSubmit}>
<input
required
className={cn(inputClasses, 'w-1/3 mr-2 px-4')}
aria-label="Your name"
placeholder="Your name..."
value={values.name}
onChange={makeOnChange('name')}
/>
<input
required
className={cn(inputClasses, 'pl-4 pr-32 flex-grow')}
aria-label="Your message"
placeholder="Your message..."
value={values.message}
onChange={makeOnChange('message')}
/>
<button
className={cn(
'flex items-center justify-center',
'absolute right-1 top-1 px-4 font-bold h-8',
'bg-gray-100 dark:bg-gray-700 text-gray-900',
'dark:text-gray-100 rounded w-28'
)}
type="submit"
disabled={isSubmitting}
>
{isSubmitting ? <LoadingSpinner /> : 'Sign'}
</button>
</form>
{{
failed: () => <ErrorMessage>Something went wrong. :(</ErrorMessage>,
submitted: () => (
<SuccessMessage>Thanks for signing the guestbook.</SuccessMessage>
),
}[formState]?.()}
</>
)
}
const Guestbook = ({ fallback }) => {
const { entries, onSubmit } = useEntriesFlow({ fallback })
return (
<SWRConfig value={{ fallback }}>
<main className="max-w-4xl mx-auto p-4">
<AppHead />
<div
className={cn(
'border border-blue-200 rounded p-6',
'my-4 w-full dark:border-gray-800 bg-blue-50',
'dark:bg-blue-opaque'
)}
>
<h5 className={cn('text-lg md:text-xl font-bold', 'text-gray-900')}>
Sign the Guestbook
</h5>
<p className="my-1 text-gray-800">
Share a message for a future visitor.
</p>
<EntryForm onSubmit={onSubmit} />
</div>
<div className="mt-4 space-y-8 px-2">
{entries?.map((entry) => (
<EntryItem key={entry._id} entry={entry} />
))}
</div>
</main>
</SWRConfig>
)
}
export async function getStaticProps() {
const entries = await listGuestbookEntries()
return {
props: {
fallback: {
entries,
},
},
}
}
export default Guestbook