chore(examples): refresh cms-prismic example (#40121)

## 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)

**Note**: `pnpm lint` fails on code unrelated to the changes in this PR.

This PR updates the [Prismic](https://prismic.io/) CMS example
(`examples/cms-prismic`) to use the latest Prismic libraries, tools, and
conventions.

- Uses `npx @slicemachine/init` to bootstrap new Prismic accounts and
content repositories.

- Uses [Slice
Machine](https://prismic.io/docs/technologies/slice-machine) to model
content.

- Uses [Slices](https://prismic.io/docs/technologies/slice) to write
post content.

- Uses the latest versions of the following packages:

  - `@prismicio/client`
  - `@prismicio/helpers`
  - `@prismicio/react`
  - `@prismicio/next`

- Adds generated TypeScript types for Prismic content. The example
remains as a JavaScript codebase (i.e. not TypeScript), but makes use of
the types via JSDoc.

Co-authored-by: Balázs Orbán <info@balazsorban.com>
This commit is contained in:
Angelo Ashmore 2022-10-01 06:06:49 +02:00 committed by GitHub
parent cfd58ae80e
commit 3ede313e1f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
71 changed files with 1490 additions and 700 deletions

View file

@ -1,3 +0,0 @@
PRISMIC_API_TOKEN=
PRISMIC_REPOSITORY_NAME=
PRISMIC_REPOSITORY_LOCALE="en-us"

View file

@ -0,0 +1,31 @@
{
"id": "mock-doc-id",
"url": null,
"type": "author",
"href": null,
"tags": [],
"first_publication_date": "1970-01-01T00:00:01+0000",
"last_publication_date": "1970-01-01T00:00:01+0000",
"slugs": [],
"linked_documents": [],
"lang": "en-us",
"alternate_languages": [],
"data": {
"name": [
{
"type": "heading1",
"text": "Border",
"spans": []
}
],
"picture": {
"dimensions": {
"width": 900,
"height": 500
},
"alt": null,
"copyright": null,
"url": "https://images.unsplash.com/photo-1587614295999-6c1c13675117"
}
}
}

View file

@ -0,0 +1,44 @@
{
"id": "mock-doc-id",
"url": null,
"type": "post",
"href": null,
"tags": [],
"first_publication_date": "1970-01-01T00:00:01+0000",
"last_publication_date": "1970-01-01T00:00:01+0000",
"slugs": [],
"linked_documents": [],
"lang": "en-us",
"alternate_languages": [],
"data": {
"title": [
{
"type": "heading1",
"text": "Shine",
"spans": []
}
],
"date": "2014-07-30",
"author": {
"id": "mock_document_id",
"link_type": "Document",
"type": "author",
"tags": [],
"lang": "en-us",
"slug": null,
"first_publication_date": "1970-01-01T00:00:01+0000",
"last_publication_date": "1970-01-01T01:00:00+0000"
},
"excerpt": "knew",
"cover_image": {
"dimensions": {
"width": 900,
"height": 500
},
"alt": null,
"copyright": null,
"url": "https://images.unsplash.com/photo-1587614295999-6c1c13675117"
},
"slices": []
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

View file

@ -0,0 +1,26 @@
import MyComponent from '../../../../slices/Image'
export default {
title: 'slices/Image',
}
export const _Default = () => (
<MyComponent
slice={{
variation: 'default',
version: 'sktwi1xtmkfgx8626',
items: [{}],
primary: {
image: {
dimensions: { width: 900, height: 500 },
alt: null,
copyright: null,
url: 'https://images.unsplash.com/photo-1493397212122-2b85dda8106b',
},
},
slice_type: 'image',
id: '_Default',
}}
/>
)
_Default.storyName = ''

View file

@ -0,0 +1,19 @@
[
{
"variation": "default",
"version": "sktwi1xtmkfgx8626",
"items": [{}],
"primary": {
"image": {
"dimensions": {
"width": 900,
"height": 500
},
"alt": null,
"copyright": null,
"url": "https://images.unsplash.com/photo-1493397212122-2b85dda8106b"
}
},
"slice_type": "image"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -0,0 +1,42 @@
import MyComponent from '../../../../slices/Text'
export default {
title: 'slices/Text',
}
export const _Default = () => (
<MyComponent
slice={{
variation: 'default',
version: 'sktwi1xtmkfgx8626',
items: [{}],
primary: {
text: [
{
type: 'paragraph',
text: 'Pariatur consequat eu eiusmod cillum velit excepteur ex sit sit dolore dolor labore ullamco.',
spans: [],
},
{
type: 'paragraph',
text: 'Et nisi elit dolor pariatur pariatur nisi non est cillum aute.',
spans: [],
},
{
type: 'paragraph',
text: 'Officia deserunt sunt ad ut anim quis.',
spans: [],
},
{
type: 'paragraph',
text: 'Est consequat enim id quis consectetur exercitation reprehenderit. Nulla ut ut consequat ipsum nulla duis consequat nostrud eiusmod eiusmod ex incididunt consequat consequat minim. Esse adipisicing ullamco officia incididunt excepteur dolor nisi magna incididunt adipisicing anim.',
spans: [],
},
],
},
slice_type: 'text',
id: '_Default',
}}
/>
)
_Default.storyName = ''

View file

@ -0,0 +1,32 @@
[
{
"variation": "default",
"version": "sktwi1xtmkfgx8626",
"items": [{}],
"primary": {
"text": [
{
"type": "paragraph",
"text": "Pariatur consequat eu eiusmod cillum velit excepteur ex sit sit dolore dolor labore ullamco.",
"spans": []
},
{
"type": "paragraph",
"text": "Et nisi elit dolor pariatur pariatur nisi non est cillum aute.",
"spans": []
},
{
"type": "paragraph",
"text": "Officia deserunt sunt ad ut anim quis.",
"spans": []
},
{
"type": "paragraph",
"text": "Est consequat enim id quis consectetur exercitation reprehenderit. Nulla ut ut consequat ipsum nulla duis consequat nostrud eiusmod eiusmod ex incididunt consequat consequat minim. Esse adipisicing ullamco officia incididunt excepteur dolor nisi magna incididunt adipisicing anim.",
"spans": []
}
]
},
"slice_type": "text"
}
]

View file

@ -0,0 +1,140 @@
{
"slices": {
"components": {
"image": {
"library": "slices",
"id": "image",
"name": "Image",
"description": "Image",
"model": {
"id": "image",
"type": "SharedSlice",
"name": "Image",
"description": "Image",
"variations": [
{
"id": "default",
"name": "Default",
"docURL": "...",
"version": "sktwi1xtmkfgx8626",
"description": "Image",
"primary": {
"image": {
"type": "Image",
"config": {
"label": "Image",
"constraint": {},
"thumbnails": []
}
}
},
"items": {},
"imageUrl": "https://images.prismic.io/slice-machine/621a5ec4-0387-4bc5-9860-2dd46cbc07cd_default_ss.png?auto=compress,format"
}
]
},
"mocks": {
"default": {
"variation": "default",
"version": "sktwi1xtmkfgx8626",
"items": [{}],
"primary": {
"image": {
"dimensions": {
"width": 900,
"height": 500
},
"alt": null,
"copyright": null,
"url": "https://images.unsplash.com/photo-1493397212122-2b85dda8106b"
}
},
"slice_type": "image"
}
},
"meta": {
"fileName": "index",
"extension": "tsx"
},
"screenshotPaths": {
"default": {
"path": "/Users/angeloashmore/projects/prismic/nextjs/examples/cms-prismic/.slicemachine/assets/slices/Image/default/preview.png",
"width": 800,
"height": 444
}
}
},
"text": {
"library": "slices",
"id": "text",
"name": "Text",
"description": "Text",
"model": {
"id": "text",
"type": "SharedSlice",
"name": "Text",
"description": "Text",
"variations": [
{
"id": "default",
"name": "Default",
"docURL": "...",
"version": "sktwi1xtmkfgx8626",
"description": "Text",
"primary": {
"text": {
"type": "StructuredText",
"config": {
"label": "Text",
"placeholder": "Rich text with formatting",
"allowTargetBlank": true,
"multi": "paragraph,preformatted,heading1,heading2,heading3,heading4,heading5,heading6,strong,em,hyperlink,image,embed,list-item,o-list-item,rtl"
}
}
},
"items": {},
"imageUrl": "https://images.prismic.io/slice-machine/621a5ec4-0387-4bc5-9860-2dd46cbc07cd_default_ss.png?auto=compress,format"
}
]
},
"mocks": {
"default": {
"variation": "default",
"version": "sktwi1xtmkfgx8626",
"items": [{}],
"primary": {
"text": [
{
"type": "paragraph",
"text": "Pariatur consequat eu eiusmod cillum velit excepteur ex sit sit dolore dolor labore ullamco.",
"spans": []
},
{
"type": "paragraph",
"text": "Et nisi elit dolor pariatur pariatur nisi non est cillum aute.",
"spans": []
},
{
"type": "paragraph",
"text": "Officia deserunt sunt ad ut anim quis.",
"spans": []
},
{
"type": "paragraph",
"text": "Est consequat enim id quis consectetur exercitation reprehenderit. Nulla ut ut consequat ipsum nulla duis consequat nostrud eiusmod eiusmod ex incididunt consequat consequat minim. Esse adipisicing ullamco officia incididunt excepteur dolor nisi magna incididunt adipisicing anim.",
"spans": []
}
]
},
"slice_type": "text"
}
},
"meta": {
"fileName": "index",
"extension": "tsx"
},
"screenshotPaths": {}
}
}
}
}

View file

@ -0,0 +1,43 @@
{
"_cts": {
"author": {
"name": {
"config": {
"patternType": "HEADING",
"blocks": 1
}
}
},
"post": {
"title": {
"config": {
"patternType": "HEADING",
"blocks": 1
}
}
}
},
"slices": {
"Text": {
"default": {
"primary": {
"text": {
"config": {
"patternType": "PARAGRAPH",
"blocks": 4
}
}
}
}
},
"Image": {
"default": {
"primary": {
"image": {
"content": "https://images.unsplash.com/photo-1493397212122-2b85dda8106b"
}
}
}
}
}
}

View file

@ -49,122 +49,79 @@ pnpm create next-app --example cms-prismic cms-prismic-app
## Configuration
### Step 1. Create an account and a repository on Prismic
### Step 1. Create an account and repository on Prismic
First, [create an account on Prismic](https://prismic.io/).
First, create a Prismic account and repository with the following command:
After creating an account, create a **repository** from the [dashboard](https://prismic.io/dashboard/) and assign to it any name of your liking.
```sh
npx @slicemachine/init
```
### Step 2. Create an `author` type
This command will:
From the repository page, create a new **custom type**:
1. Ask you to log in to Prismic or create an account.
2. Create a new Prismic repository with premade Author and Post content models.
3. Connect the Prismic repository to your app.
- The name should be `author`.
**Optional**: To see the premade content models, start the [Slice Machine](https://prismic.io/docs/technologies/slice-machine) app.
Next, add these fields (you don't have to modify the settings):
```sh
npm run slicemachine
```
- `name` - **Key Text** field
- `picture` - **Image** field
Slice Machine should be available on <http://localhost:9999> once started.
Alternatively, you can copy the JSON in [`types/author.json`](types/author.json), then click on **JSON editor** and paste it there.
### Step 2. Populate Content
Save the type and continue.
Go to the [Prismic dashboard](https://prismic.io/dashboard) and select your Prismic repository.
### Step 3. Create a `post` type
From the repository page, create a new **custom type**:
- The name should be `post`.
Next, add these fields (you don't have to modify the settings unless specified):
- `title` - **Title** field
- `content` - **Rich Text** field
- `excerpt` - **Key Text** field
- `coverimage` - **Image** field
- `date` - **Date** field
- `author` - **Content relationship** field, you may also add `author` to the **Constraint to custom type** option to only accept documents from the `author` type.
- `slug` - **UID** field.
Alternatively, you can copy the JSON in [`types/post.json`](types/post.json), then click on **JSON editor** and paste it there.
Save the type and continue.
### Step 4. Populate Content
Go to the **Content** page, it's in the menu at the top left, then click on **Create new** and select the **author** type:
Once in, click on **Create new** and select the **Author** type:
- You just need **1 author document**.
- Use dummy data for the text.
- For the image, you can download one from [Unsplash](https://unsplash.com/).
Next, select **Post** and create a new document.
Save and publish the document.
Next, go back to the documents list, click on **Create new** and select the **Post** type:
- We recommend creating at least **2 Post documents**.
- Use dummy data for the text.
- You can write markdown for the **content** field.
- You can use the Text and Image Slices to create content.
- For images, you can download them from [Unsplash](https://unsplash.com/).
- Pick the **author** you created earlier.
**Important:** For each document, you need to click **Publish** after saving. If not, the document will be in the draft state.
### Step 5. Set up environment variables
Follow the instructions in [this post](https://intercom.help/prismicio/en/articles/1036153-generating-an-access-token) to generate a new access token.
Next, copy the `.env.local.example` file in this directory to `.env.local` (which will be ignored by Git):
### Step 3. Run Next.js in development mode
```bash
cp .env.local.example .env.local
```
Then set each variable on `.env.local`:
- `PRISMIC_API_TOKEN` should be the **Permanent access token** you just created
- `PRISMIC_REPOSITORY_NAME` is the name of your repository (the one in the URL)
- `PRISMIC_REPOSITORY_LOCALE` is the locale of your repository. Defaults to `en-us`
Your `.env.local` file should look like this:
```bash
PRISMIC_API_TOKEN=...
PRISMIC_REPOSITORY_NAME=...
PRISMIC_REPOSITORY_LOCALE=...
```
Make sure the locale matches your settings in the Prismic dashboard.
### Step 6. Run Next.js in development mode
```bash
npm install
npm run dev
# or
yarn install
yarn dev
```
Your blog should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions).
### Step 7. Try preview mode
### Step 4. Try preview mode
On your repository page, go to **Settings**, click on **Previews** and then **Create a New Preview** for development, fill the form like so:
On your repository page, go to **Settings**, click on **Previews**, and then **Create a New Preview**. Fill the form like so for development previews:
- **Site Name**: may be anything, like `development`
- **Site Name**: may be anything, like "Development"
- **Domain of Your Application**: `http://localhost:3000`
- **Link Resolver**: `/api/preview`
Once saved, go to one of the posts you've created and:
Once saved, go to one of the posts you created and:
- **Update the title**. For example, you can add `[Draft]` in front of the title.
- Click **Save**, but **DO NOT** click **Publish**. By doing this, the post will be in draft state.
- Right next to the **Publish** button you should see the **Preview** button, displayed with an eye icon. Click on it!
- Right next to the **Publish** button, you should see the **Preview** button, displayed with an eye icon. Click on it!
You should now be able to see the updated title. To exit preview mode, you can click on **Click here to exit preview mode** at the top of the page.
You should now be able to see the updated title. To exit preview mode, click on the "x" icon in the purple Prismic toolbar in the bottom left corner of the page.
### Step 8. Deploy on Vercel
### Step 5. Deploy on Vercel
You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
@ -172,10 +129,8 @@ You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source
To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example).
**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file.
#### Deploy from Our Template
Alternatively, you can deploy using our template by clicking on the Deploy button below.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-prismic&project-name=cms-prismic&repository-name=cms-prismic&env=PRISMIC_API_TOKEN,PRISMIC_REPOSITORY_NAME&envDescription=Required%20to%20connect%20the%20app%20with%20Prismic&envLink=https://vercel.link/cms-prismic-env)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-prismic&project-name=cms-prismic)

View file

@ -1,8 +1,14 @@
import Container from './container'
import cn from 'classnames'
import { EXAMPLE_PATH } from '../lib/constants'
export default function Alert({ preview }) {
import Container from './container'
type AlertProps = {
preview: boolean
}
export default function Alert({ preview }: AlertProps) {
return (
<div
className={cn('border-b', {

View file

@ -1,17 +0,0 @@
import Image from 'next/image'
export default function Avatar({ name, picture }) {
return (
<div className="flex items-center">
<div className="w-12 h-12 relative mr-4">
<Image
src={picture.url}
layout="fill"
className="rounded-full"
alt={name[0].text}
/>
</div>
<div className="text-xl font-bold">{name}</div>
</div>
)
}

View file

@ -0,0 +1,22 @@
import { PrismicNextImage } from '@prismicio/next'
import { ImageField } from '@prismicio/types'
type AvatarProps = {
name: string
picture: ImageField
}
export default function Avatar({ name, picture }: AvatarProps) {
return (
<div className="flex items-center">
<div className="w-12 h-12 relative mr-4">
<PrismicNextImage
field={picture}
layout="fill"
className="rounded-full"
/>
</div>
<div className="text-xl font-bold">{name}</div>
</div>
)
}

View file

@ -1,3 +0,0 @@
export default function Container({ children }) {
return <div className="container mx-auto px-5">{children}</div>
}

View file

@ -0,0 +1,7 @@
type ContainerProps = {
children: React.ReactNode
}
export default function Container({ children }: ContainerProps) {
return <div className="container mx-auto px-5">{children}</div>
}

View file

@ -1,29 +0,0 @@
import Image from 'next/image'
import Link from 'next/link'
import cn from 'classnames'
export default function CoverImage({ title, url, slug }) {
const image = (
<Image
width={2000}
height={1000}
alt={`Cover Image for ${title}`}
className={cn('shadow-small', {
'hover:shadow-medium transition-shadow duration-200': slug,
})}
src={url}
/>
)
return (
<div className="sm:mx-0">
{slug ? (
<Link href={`/posts/${slug}`}>
<a aria-label={title}>{image}</a>
</Link>
) : (
image
)}
</div>
)
}

View file

@ -0,0 +1,40 @@
import Link from 'next/link'
import { PrismicNextImage } from '@prismicio/next'
import { ImageField } from '@prismicio/types'
import cn from 'classnames'
type CoverImageProps = {
title: string
image: ImageField
href?: string
}
export default function CoverImage({
title,
image: imageField,
href,
}: CoverImageProps) {
const image = (
<PrismicNextImage
field={imageField}
width={2000}
height={1000}
imgixParams={{ fit: 'crop', ar: '2:1' }}
className={cn('shadow-small', {
'hover:shadow-medium transition-shadow duration-200': href,
})}
/>
)
return (
<div className="sm:mx-0">
{href ? (
<Link href={href}>
<a aria-label={title}>{image}</a>
</Link>
) : (
image
)}
</div>
)
}

View file

@ -1,7 +0,0 @@
import { format } from 'date-fns'
import { Date as PrismicDate } from 'prismic-reactjs'
export default function Date({ dateString }) {
const date = PrismicDate(dateString)
return <time dateTime={dateString}>{format(date, 'LLLL d, yyyy')}</time>
}

View file

@ -0,0 +1,18 @@
import { asDate } from '@prismicio/helpers'
import { DateField } from '@prismicio/types'
const formatter = new Intl.DateTimeFormat('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
})
type DateProps = {
dateField: DateField
}
export default function Date({ dateField }: DateProps) {
const date = asDate(dateField)
return <time dateTime={dateField}>{formatter.format(date)}</time>
}

View file

@ -1,6 +1,7 @@
import Container from './container'
import { EXAMPLE_PATH } from '../lib/constants'
import Container from './container'
export default function Footer() {
return (
<footer className="bg-accent-1 border-t border-accent-2">

View file

@ -1,44 +0,0 @@
import Link from 'next/link'
import { RichText } from 'prismic-reactjs'
import Avatar from '../components/avatar'
import Date from '../components/date'
import CoverImage from '../components/cover-image'
export default function HeroPost({
title,
coverImage,
date,
excerpt,
author,
slug,
}) {
return (
<section>
<div className="mb-8 md:mb-16">
<CoverImage
title={RichText.asText(title)}
slug={slug}
url={coverImage.url}
/>
</div>
<div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28">
<div>
<h3 className="mb-4 text-4xl lg:text-6xl leading-tight">
<Link href={`/posts/${slug}`}>
<a className="hover:underline">
<RichText render={title} />
</a>
</Link>
</h3>
<div className="mb-4 md:mb-0 text-lg">
<Date dateString={date} />
</div>
</div>
<div>
<p className="text-lg leading-relaxed mb-4">{excerpt}</p>
{author && <Avatar name={author.name} picture={author.picture} />}
</div>
</div>
</section>
)
}

View file

@ -0,0 +1,59 @@
import Link from 'next/link'
import { DateField, ImageField, TitleField } from '@prismicio/types'
import { PrismicText } from '@prismicio/react'
import { asText, isFilled } from '@prismicio/helpers'
import { AuthorContentRelationshipField } from '../lib/types'
import Avatar from '../components/avatar'
import CoverImage from '../components/cover-image'
import Date from '../components/date'
type HeroPostProps = {
title: TitleField
coverImage: ImageField
date: DateField
excerpt: string
author: AuthorContentRelationshipField
href: string
}
export default function HeroPost({
title,
coverImage,
date,
excerpt,
author,
href,
}: HeroPostProps) {
return (
<section>
<div className="mb-8 md:mb-16">
<CoverImage title={asText(title)} href={href} image={coverImage} />
</div>
<div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28">
<div>
<h3 className="mb-4 text-4xl lg:text-6xl leading-tight">
<Link href={href}>
<a className="hover:underline">
<PrismicText field={title} />
</a>
</Link>
</h3>
<div className="mb-4 md:mb-0 text-lg">
<Date dateField={date} />
</div>
</div>
<div>
<p className="text-lg leading-relaxed mb-4">{excerpt}</p>
{isFilled.contentRelationship(author) && (
<Avatar
name={asText(author.data.name)}
picture={author.data.picture}
/>
)}
</div>
</div>
</section>
)
}

View file

@ -2,7 +2,12 @@ import Alert from '../components/alert'
import Footer from '../components/footer'
import Meta from '../components/meta'
export default function Layout({ preview, children }) {
type LayoutProps = {
preview: boolean
children: React.ReactNode
}
export default function Layout({ preview, children }: LayoutProps) {
return (
<>
<Meta />

View file

@ -1,18 +0,0 @@
.markdown {
@apply text-lg leading-relaxed;
}
.markdown p,
.markdown ul,
.markdown ol,
.markdown blockquote {
@apply my-6;
}
.markdown h2 {
@apply text-3xl mt-12 mb-4 leading-snug;
}
.markdown h3 {
@apply text-2xl mt-8 mb-4 leading-snug;
}

View file

@ -1,4 +1,5 @@
import Head from 'next/head'
import { CMS_NAME, HOME_OG_IMAGE_URL } from '../lib/constants'
export default function Meta() {

View file

@ -1,24 +0,0 @@
import PostPreview from '../components/post-preview'
export default function MoreStories({ posts }) {
return (
<section>
<h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight">
More Stories
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32">
{posts.map(({ node }) => (
<PostPreview
key={node._meta.uid}
title={node.title}
coverImage={node.coverimage}
date={node.date}
author={node.author}
slug={node._meta.uid}
excerpt={node.excerpt}
/>
))}
</div>
</section>
)
}

View file

@ -0,0 +1,30 @@
import { Content } from '@prismicio/client'
import PostPreview from '../components/post-preview'
type MoreStoriesProps = {
posts: Content.PostDocument[]
}
export default function MoreStories({ posts }: MoreStoriesProps) {
return (
<section>
<h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight">
More Stories
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32">
{posts.map((post) => (
<PostPreview
key={post.uid}
href={post.url}
title={post.data.title}
coverImage={post.data.cover_image}
date={post.data.date}
author={post.data.author}
excerpt={post.data.excerpt}
/>
))}
</div>
</section>
)
}

View file

@ -1,12 +0,0 @@
import markdownStyles from './markdown-styles.module.css'
import { RichText } from 'prismic-reactjs'
export default function PostBody({ content }) {
return (
<div className="max-w-2xl mx-auto">
<div className={markdownStyles['markdown']}>
<RichText render={content} />
</div>
</div>
)
}

View file

@ -0,0 +1,16 @@
import { SliceZone } from '@prismicio/react'
import { Content } from '@prismicio/client'
import { components } from '../slices'
type PostBodyProps = {
slices: Content.PostDocument['data']['slices']
}
export default function PostBody({ slices }: PostBodyProps) {
return (
<div className="max-w-2xl mx-auto">
<SliceZone slices={slices} components={components} />
</div>
)
}

View file

@ -1,27 +0,0 @@
import { RichText } from 'prismic-reactjs'
import Avatar from '../components/avatar'
import Date from '../components/date'
import CoverImage from '../components/cover-image'
import PostTitle from '../components/post-title'
export default function PostHeader({ title, coverImage, date, author }) {
return (
<>
<PostTitle>{title[0].text}</PostTitle>
<div className="hidden md:block md:mb-12">
{author && <Avatar name={author.name} picture={author.picture} />}
</div>
<div className="mb-8 md:mb-16 sm:mx-0">
<CoverImage title={RichText.asText(title)} url={coverImage.url} />
</div>
<div className="max-w-2xl mx-auto">
<div className="block md:hidden mb-6">
{author && <Avatar name={author.name} picture={author.picture} />}
</div>
<div className="mb-6 text-lg">
<Date dateString={date} />
</div>
</div>
</>
)
}

View file

@ -0,0 +1,56 @@
import { PrismicText } from '@prismicio/react'
import { asText, isFilled } from '@prismicio/helpers'
import { DateField, ImageField, TitleField } from '@prismicio/types'
import { AuthorContentRelationshipField } from '../lib/types'
import Avatar from '../components/avatar'
import Date from '../components/date'
import CoverImage from '../components/cover-image'
import PostTitle from '../components/post-title'
type PostHeaderProps = {
title: TitleField
coverImage: ImageField
date: DateField
author: AuthorContentRelationshipField
}
export default function PostHeader({
title,
coverImage,
date,
author,
}: PostHeaderProps) {
return (
<>
<PostTitle>
<PrismicText field={title} />
</PostTitle>
<div className="hidden md:block md:mb-12">
{isFilled.contentRelationship(author) && (
<Avatar
name={asText(author.data.name)}
picture={author.data.picture}
/>
)}
</div>
<div className="mb-8 md:mb-16 sm:mx-0">
<CoverImage title={asText(title)} image={coverImage} />
</div>
<div className="max-w-2xl mx-auto">
<div className="block md:hidden mb-6">
{isFilled.contentRelationship(author) && (
<Avatar
name={asText(author.data.name)}
picture={author.data.picture}
/>
)}
</div>
<div className="mb-6 text-lg">
<Date dateField={date} />
</div>
</div>
</>
)
}

View file

@ -1,38 +0,0 @@
import Link from 'next/link'
import { RichText } from 'prismic-reactjs'
import Avatar from '../components/avatar'
import Date from '../components/date'
import CoverImage from './cover-image'
export default function PostPreview({
title,
coverImage,
date,
excerpt,
author,
slug,
}) {
return (
<div>
<div className="mb-5">
<CoverImage
title={RichText.asText(title)}
slug={slug}
url={coverImage.url}
/>
</div>
<h3 className="text-3xl mb-3 leading-snug">
<Link href={`/posts/${slug}`}>
<a className="hover:underline">
<RichText render={title} />
</a>
</Link>
</h3>
<div className="text-lg mb-4">
<Date dateString={date} />
</div>
<p className="text-lg leading-relaxed mb-4">{excerpt}</p>
<Avatar name={author.name} picture={author.picture} />
</div>
)
}

View file

@ -0,0 +1,51 @@
import Link from 'next/link'
import { DateField, ImageField, TitleField } from '@prismicio/types'
import { PrismicText } from '@prismicio/react'
import { asText, isFilled } from '@prismicio/helpers'
import { AuthorContentRelationshipField } from '../lib/types'
import Avatar from '../components/avatar'
import Date from '../components/date'
import CoverImage from './cover-image'
type PostPreviewProps = {
title: TitleField
coverImage: ImageField
date: DateField
excerpt: string
author: AuthorContentRelationshipField
href: string
}
export default function PostPreview({
title,
coverImage,
date,
excerpt,
author,
href,
}: PostPreviewProps) {
return (
<div>
<div className="mb-5">
<CoverImage title={asText(title)} href={href} image={coverImage} />
</div>
<h3 className="text-3xl mb-3 leading-snug">
<Link href={href}>
<a className="hover:underline">
<PrismicText field={title} />
</a>
</Link>
</h3>
<div className="text-lg mb-4">
<Date dateField={date} />
</div>
<p className="text-lg leading-relaxed mb-4">{excerpt}</p>
{isFilled.contentRelationship(author) && (
<Avatar name={asText(author.data.name)} picture={author.data.picture} />
)}
</div>
)
}

View file

@ -1,4 +1,8 @@
export default function PostTitle({ children }) {
type PostTitleProps = {
children: React.ReactNode
}
export default function PostTitle({ children }: PostTitleProps) {
return (
<h1 className="text-6xl md:text-7xl lg:text-8xl font-bold tracking-tighter leading-tight md:leading-none mb-12 text-center md:text-left">
{children}

View file

@ -0,0 +1,27 @@
{
"id": "author",
"label": "Author",
"repeatable": true,
"status": true,
"json": {
"Main": {
"name": {
"type": "StructuredText",
"config": {
"label": "Name",
"placeholder": "Name of the author",
"allowTargetBlank": false,
"single": "heading1"
}
},
"picture": {
"type": "Image",
"config": {
"label": "Picture",
"constraint": {},
"thumbnails": []
}
}
}
}
}

View file

@ -0,0 +1,70 @@
{
"id": "post",
"label": "Post",
"repeatable": true,
"status": true,
"json": {
"Main": {
"title": {
"type": "StructuredText",
"config": {
"label": "Title",
"placeholder": "Title of the post",
"allowTargetBlank": false,
"single": "heading1"
}
},
"uid": {
"type": "UID",
"config": {
"label": "UID",
"placeholder": "URL slug for the post"
}
},
"date": {
"type": "Date",
"config": {
"label": "Date",
"placeholder": "Date of publication"
}
},
"author": {
"type": "Link",
"config": {
"label": "Author",
"select": "document",
"customtypes": ["author"]
}
},
"excerpt": {
"type": "Text",
"config": {
"label": "Excerpt",
"placeholder": "Short summary of the post"
}
},
"cover_image": {
"type": "Image",
"config": {
"label": "Cover Image",
"constraint": {},
"thumbnails": []
}
},
"slices": {
"type": "Slices",
"fieldset": "Slice Zone",
"config": {
"choices": {
"text": {
"type": "SharedSlice"
},
"image": {
"type": "SharedSlice"
}
}
}
}
}
}
}

View file

@ -0,0 +1 @@
This directory can be removed after running `npx @slicemachine/init`.

View file

@ -1,146 +0,0 @@
import Prismic from 'prismic-javascript'
const REPOSITORY = process.env.PRISMIC_REPOSITORY_NAME
const REF_API_URL = `https://${REPOSITORY}.cdn.prismic.io/api/v2`
const GRAPHQL_API_URL = `https://${REPOSITORY}.cdn.prismic.io/graphql`
// export const API_URL = 'https://your-repo-name.cdn.prismic.io/api/v2'
export const API_TOKEN = process.env.PRISMIC_API_TOKEN
export const API_LOCALE = process.env.PRISMIC_REPOSITORY_LOCALE
export const PrismicClient = Prismic.client(REF_API_URL, {
accessToken: API_TOKEN,
})
async function fetchAPI(query, { previewData, variables } = {}) {
const prismicAPI = await PrismicClient.getApi()
const res = await fetch(
`${GRAPHQL_API_URL}?query=${query}&variables=${JSON.stringify(variables)}`,
{
headers: {
'Prismic-Ref': previewData?.ref || prismicAPI.masterRef.ref,
'Content-Type': 'application/json',
'Accept-Language': API_LOCALE,
Authorization: `Token ${API_TOKEN}`,
},
}
)
if (res.status !== 200) {
console.log(await res.text())
throw new Error('Failed to fetch API')
}
const json = await res.json()
if (json.errors) {
console.error(json.errors)
throw new Error('Failed to fetch API')
}
return json.data
}
export async function getAllPostsWithSlug() {
const data = await fetchAPI(`
{
allPosts {
edges {
node {
_meta {
uid
}
}
}
}
}
`)
return data?.allPosts?.edges
}
export async function getAllPostsForHome(previewData) {
const data = await fetchAPI(
`
query {
allPosts(sortBy: date_DESC) {
edges {
node {
date
title
content
coverimage
excerpt
author {
...on Author {
name
picture
}
}
_meta {
uid
}
}
}
}
}
`,
{ previewData }
)
return data.allPosts.edges
}
export async function getPostAndMorePosts(slug, previewData) {
const data = await fetchAPI(
`
query PostBySlug($slug: String!, $lang: String!) {
post(uid: $slug, lang: $lang) {
title
content
date
coverimage
author {
...on Author {
name
picture
}
}
_meta {
uid
}
}
morePosts: allPosts(sortBy: date_DESC, first: 3) {
edges {
node {
title
content
date
coverimage
excerpt
author {
...on Author {
name
picture
}
}
_meta {
uid
}
}
}
}
}
`,
{
previewData,
variables: {
slug,
lang: API_LOCALE,
},
}
)
data.morePosts = data.morePosts.edges
.filter(({ node }) => node._meta.uid !== slug)
.slice(0, 2)
return data
}

View file

@ -0,0 +1,40 @@
import * as prismic from '@prismicio/client'
import * as prismicNext from '@prismicio/next'
import sm from '../sm.json'
/**
* The project's Prismic repository name.
*/
export const repositoryName = prismic.getRepositoryName(sm.apiEndpoint)
/**
* Route definitions for Prismic documents.
*/
const routes: prismic.Route[] = [
{
type: 'post',
path: '/posts/:uid',
},
]
/**
* Creates a Prismic client for the project's repository. The client is used to
* query content from the Prismic API.
*
* @param config - Configuration for the Prismic client.
*/
export const createClient = ({
previewData,
req,
...config
}: prismicNext.CreateClientConfig = {}) => {
const client = prismic.createClient(sm.apiEndpoint, {
routes,
...config,
})
prismicNext.enableAutoPreviews({ client, previewData, req })
return client
}

View file

@ -0,0 +1,17 @@
import { Content } from '@prismicio/client'
import { ImageField, RelationField, TitleField } from '@prismicio/types'
export type PostDocumentWithAuthor = Content.PostDocument & {
data: {
author: AuthorContentRelationshipField
}
}
export type AuthorContentRelationshipField = RelationField<
'author',
string,
{
name: TitleField
picture: ImageField
}
>

View file

@ -3,20 +3,27 @@
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
"start": "next start",
"slicemachine": "start-slicemachine"
},
"dependencies": {
"classnames": "2.3.1",
"date-fns": "2.28.0",
"@prismicio/client": "^6.7.1",
"@prismicio/helpers": "^2.3.3",
"@prismicio/next": "^0.1.5",
"@prismicio/react": "^2.5.0",
"@prismicio/slice-simulator-react": "^0.2.2",
"@prismicio/types": "^0.2.3",
"classnames": "2.3.2",
"next": "latest",
"prismic-javascript": "3.0.2",
"prismic-reactjs": "1.3.4",
"react": "^17.0.2",
"react-dom": "^17.0.2"
"prismic-ts-codegen": "^0.1.5",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"autoprefixer": "10.4.2",
"postcss": "8.4.5",
"tailwindcss": "^3.0.15"
"autoprefixer": "10.4.10",
"postcss": "8.4.16",
"slice-machine-ui": "^0.4.2",
"tailwindcss": "^3.1.8",
"typescript": "^4.8.3"
}
}

View file

@ -1,7 +0,0 @@
import '../styles/index.css'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp

View file

@ -0,0 +1,13 @@
import { PrismicPreview } from '@prismicio/next'
import { repositoryName } from '../lib/prismic'
import '../styles/index.css'
function MyApp({ Component, pageProps }) {
return (
<PrismicPreview repositoryName={repositoryName}>
<Component {...pageProps} />
</PrismicPreview>
)
}
export default MyApp

View file

@ -1,8 +0,0 @@
export default async function exit(_, res) {
// Exit the current user from "Preview Mode". This function accepts no args.
res.clearPreviewData()
// Redirect the user back to the index page.
res.writeHead(307, { Location: '/' })
res.end()
}

View file

@ -0,0 +1,6 @@
import { NextApiRequest, NextApiResponse } from 'next'
import { exitPreview } from '@prismicio/next'
export default async function exit(req: NextApiRequest, res: NextApiResponse) {
exitPreview({ res, req })
}

View file

@ -1,41 +0,0 @@
import { PrismicClient } from '../../lib/api'
function linkResolver(doc) {
// Pretty URLs for known types
if (doc.type === 'post') {
return `/posts/${doc.uid}`
}
// Fallback for other types, in case new custom types get created
return `/${doc.uid}`
}
export default async function preview(req, res) {
const { token: ref, documentId } = req.query
// Check the token parameter against the Prismic SDK
const url = await PrismicClient.getPreviewResolver(ref, documentId).resolve(
linkResolver,
'/'
)
if (!url) {
return res.status(401).json({ message: 'Invalid token' })
}
// Enable Preview Mode by setting the cookies
res.setPreviewData({
ref, // pass the ref to pages so that they can fetch the draft ref
})
// Redirect the user to the share endpoint from same origin. This is
// necessary due to a Chrome bug:
// https://bugs.chromium.org/p/chromium/issues/detail?id=696204
res.write(
`<!DOCTYPE html><html><head><meta http-equiv="Refresh" content="0; url=${url}" />
<script>window.location.href = '${url}'</script>
</head>`
)
res.end()
}

View file

@ -0,0 +1,15 @@
import { NextApiRequest, NextApiResponse } from 'next'
import { setPreviewData, redirectToPreviewURL } from '@prismicio/next'
import { createClient } from '../../lib/prismic'
export default async function preview(
req: NextApiRequest,
res: NextApiResponse
) {
const client = createClient({ req })
setPreviewData({ req, res })
await redirectToPreviewURL({ req, res, client })
}

View file

@ -1,43 +0,0 @@
import Container from '../components/container'
import MoreStories from '../components/more-stories'
import HeroPost from '../components/hero-post'
import Intro from '../components/intro'
import Layout from '../components/layout'
import { getAllPostsForHome } from '../lib/api'
import Head from 'next/head'
import { CMS_NAME } from '../lib/constants'
export default function Index({ preview, allPosts }) {
const heroPost = allPosts[0].node
const morePosts = allPosts.slice(1)
return (
<>
<Layout preview={preview}>
<Head>
<title>Next.js Blog Example with {CMS_NAME}</title>
</Head>
<Container>
<Intro />
{heroPost && (
<HeroPost
title={heroPost.title}
coverImage={heroPost.coverimage}
date={heroPost.date}
author={heroPost.author}
slug={heroPost._meta.uid}
excerpt={heroPost.excerpt}
/>
)}
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
</Container>
</Layout>
</>
)
}
export async function getStaticProps({ preview = false, previewData }) {
const allPosts = await getAllPostsForHome(previewData)
return {
props: { preview, allPosts },
}
}

View file

@ -0,0 +1,59 @@
import Head from 'next/head'
import Container from '../components/container'
import MoreStories from '../components/more-stories'
import HeroPost from '../components/hero-post'
import Intro from '../components/intro'
import Layout from '../components/layout'
import { CMS_NAME } from '../lib/constants'
import { createClient } from '../lib/prismic'
import { PostDocumentWithAuthor } from '../lib/types'
import { GetStaticPropsContext, GetStaticPropsResult } from 'next'
type IndexProps = {
preview: boolean
allPosts: PostDocumentWithAuthor[]
}
export default function Index({ preview, allPosts }: IndexProps) {
const [heroPost, ...morePosts] = allPosts
return (
<>
<Layout preview={preview}>
<Head>
<title>Next.js Blog Example with {CMS_NAME}</title>
</Head>
<Container>
<Intro />
{heroPost && (
<HeroPost
title={heroPost.data.title}
href={heroPost.url}
coverImage={heroPost.data.cover_image}
date={heroPost.data.date}
author={heroPost.data.author}
excerpt={heroPost.data.excerpt}
/>
)}
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
</Container>
</Layout>
</>
)
}
export async function getStaticProps({
preview = false,
previewData,
}: GetStaticPropsContext): Promise<GetStaticPropsResult<IndexProps>> {
const client = createClient({ previewData })
const allPosts = await client.getAllByType('post', {
fetchLinks: ['author.name', 'author.picture'],
orderings: [{ field: 'my.post.date', direction: 'desc' }],
})
return {
props: { preview, allPosts },
}
}

View file

@ -1,73 +0,0 @@
import { useRouter } from 'next/router'
import Head from 'next/head'
import ErrorPage from 'next/error'
import Container from '../../components/container'
import PostBody from '../../components/post-body'
import MoreStories from '../../components/more-stories'
import Header from '../../components/header'
import PostHeader from '../../components/post-header'
import SectionSeparator from '../../components/section-separator'
import Layout from '../../components/layout'
import { getAllPostsWithSlug, getPostAndMorePosts } from '../../lib/api'
import PostTitle from '../../components/post-title'
import { CMS_NAME } from '../../lib/constants'
export default function Post({ post, morePosts, preview }) {
const router = useRouter()
if (!router.isFallback && !post?._meta?.uid) {
return <ErrorPage statusCode={404} />
}
return (
<Layout preview={preview}>
<Container>
<Header />
{router.isFallback ? (
<PostTitle>Loading</PostTitle>
) : (
<>
<article>
<Head>
<title>
{post.title[0].text} | Next.js Blog Example with {CMS_NAME}
</title>
<meta property="og:image" content={post.coverimage.url} />
</Head>
<PostHeader
title={post.title}
coverImage={post.coverimage}
date={post.date}
author={post.author}
/>
<PostBody content={post.content} />
</article>
<SectionSeparator />
{morePosts && morePosts.length > 0 && (
<MoreStories posts={morePosts} />
)}
</>
)}
</Container>
</Layout>
)
}
export async function getStaticProps({ params, preview = false, previewData }) {
const data = await getPostAndMorePosts(params.slug, previewData)
return {
props: {
preview,
post: data?.post ?? null,
morePosts: data?.morePosts ?? [],
},
}
}
export async function getStaticPaths() {
const allPosts = await getAllPostsWithSlug()
return {
paths: allPosts?.map(({ node }) => `/posts/${node._meta.uid}`) || [],
fallback: true,
}
}

View file

@ -0,0 +1,112 @@
import { GetStaticPropsContext, GetStaticPropsResult } from 'next'
import Head from 'next/head'
import { useRouter } from 'next/router'
import { predicate } from '@prismicio/client'
import { asImageSrc, asText } from '@prismicio/helpers'
import { CMS_NAME } from '../../lib/constants'
import { PostDocumentWithAuthor } from '../../lib/types'
import { createClient } from '../../lib/prismic'
import Container from '../../components/container'
import Header from '../../components/header'
import Layout from '../../components/layout'
import MoreStories from '../../components/more-stories'
import PostBody from '../../components/post-body'
import PostHeader from '../../components/post-header'
import PostTitle from '../../components/post-title'
import SectionSeparator from '../../components/section-separator'
type PostProps = {
preview: boolean
post: PostDocumentWithAuthor
morePosts: PostDocumentWithAuthor[]
}
export default function Post({ post, morePosts, preview }: PostProps) {
const router = useRouter()
return (
<Layout preview={preview}>
<Container>
<Header />
{router.isFallback ? (
<PostTitle>Loading</PostTitle>
) : (
<>
<article>
<Head>
<title>
{asText(post.data.title)} | Next.js Blog Example with{' '}
{CMS_NAME}
</title>
<meta
property="og:image"
content={asImageSrc(post.data.cover_image, {
width: 1200,
height: 600,
fit: 'crop',
})}
/>
</Head>
<PostHeader
title={post.data.title}
coverImage={post.data.cover_image}
date={post.data.date}
author={post.data.author}
/>
<PostBody slices={post.data.slices} />
</article>
<SectionSeparator />
{morePosts && morePosts.length > 0 && (
<MoreStories posts={morePosts} />
)}
</>
)}
</Container>
</Layout>
)
}
export async function getStaticProps({
params,
preview = false,
previewData,
}: GetStaticPropsContext<{ slug: string }>): Promise<
GetStaticPropsResult<PostProps>
> {
const client = createClient({ previewData })
const [post, morePosts] = await Promise.all([
client.getByUID<PostDocumentWithAuthor>('post', params.slug, {
fetchLinks: ['author.name', 'author.picture'],
}),
client.getAllByType<PostDocumentWithAuthor>('post', {
fetchLinks: ['author.name', 'author.picture'],
orderings: [{ field: 'my.post.date', direction: 'desc' }],
predicates: [predicate.not('my.post.uid', params.slug)],
limit: 2,
}),
])
if (!post) {
return {
notFound: true,
}
} else {
return {
props: { preview, post, morePosts },
}
}
}
export async function getStaticPaths() {
const client = createClient()
const allPosts = await client.getAllByType('post')
return {
paths: allPosts.map((post) => post.url),
fallback: true,
}
}

View file

@ -0,0 +1,14 @@
import { SliceSimulator } from '@prismicio/slice-simulator-react'
import { SliceZone } from '@prismicio/react'
import { components } from '../slices'
import state from '../.slicemachine/libraries-state.json'
export default function SliceSimulatorPage() {
return (
<SliceSimulator
sliceZone={(props) => <SliceZone {...props} components={components} />}
state={state}
/>
)
}

View file

@ -0,0 +1,8 @@
import type { Config } from 'prismic-ts-codegen'
const config: Config = {
output: './types.generated.ts',
models: ['./customtypes/**/index.json', './slices/**/model.json'],
}
export default config

View file

@ -0,0 +1,15 @@
import { PrismicNextImage } from '@prismicio/next'
import { SliceComponentProps } from '@prismicio/react'
import { Content } from '@prismicio/client'
type ImageProps = SliceComponentProps<Content.ImageSlice>
const Image = ({ slice }: ImageProps) => {
return (
<section className="my-12">
<PrismicNextImage field={slice.primary.image} layout="responsive" />
</section>
)
}
export default Image

View file

@ -0,0 +1,27 @@
{
"id": "image",
"type": "SharedSlice",
"name": "Image",
"description": "Image",
"variations": [
{
"id": "default",
"name": "Default",
"docURL": "...",
"version": "sktwi1xtmkfgx8626",
"description": "Image",
"primary": {
"image": {
"type": "Image",
"config": {
"label": "Image",
"constraint": {},
"thumbnails": []
}
}
},
"items": {},
"imageUrl": "https://images.prismic.io/slice-machine/621a5ec4-0387-4bc5-9860-2dd46cbc07cd_default_ss.png?auto=compress,format"
}
]
}

View file

@ -0,0 +1,27 @@
import { PrismicRichText, SliceComponentProps } from '@prismicio/react'
import { Content } from '@prismicio/client'
type TextProps = SliceComponentProps<Content.TextSlice>
const Text = ({ slice }: TextProps) => {
return (
<section className="text-lg leading-relaxed">
<PrismicRichText
field={slice.primary.text}
components={{
heading2: ({ children }) => (
<h2 className="text-3xl mt-12 mb-4 leading-snug">{children}</h2>
),
heading3: ({ children }) => (
<h2 className="text-2xl mt-8 mb-4 leading-snug">{children}</h2>
),
paragraph: ({ children }) => <p className="my-6">{children}</p>,
list: ({ children }) => <ul className="my-6">{children}</ul>,
oList: ({ children }) => <ol className="my-6">{children}</ol>,
}}
/>
</section>
)
}
export default Text

View file

@ -0,0 +1,28 @@
{
"id": "text",
"type": "SharedSlice",
"name": "Text",
"description": "Text",
"variations": [
{
"id": "default",
"name": "Default",
"docURL": "...",
"version": "sktwi1xtmkfgx8626",
"description": "Text",
"primary": {
"text": {
"type": "StructuredText",
"config": {
"label": "Text",
"placeholder": "Rich text with formatting",
"allowTargetBlank": true,
"multi": "paragraph,preformatted,heading1,heading2,heading3,heading4,heading5,heading6,strong,em,hyperlink,image,embed,list-item,o-list-item,rtl"
}
}
},
"items": {},
"imageUrl": "https://images.prismic.io/slice-machine/621a5ec4-0387-4bc5-9860-2dd46cbc07cd_default_ss.png?auto=compress,format"
}
]
}

View file

@ -0,0 +1,11 @@
// This file is generated by Slice Machine. Update with care!
import Image from './Image'
import Text from './Text'
export { Image, Text }
export const components = {
image: Image,
text: Text,
}

View file

@ -0,0 +1,6 @@
{
"apiEndpoint": "https://nextjs-example-cms-prismic.prismic.io/api/v2",
"libraries": ["@/slices"],
"localSliceSimulatorURL": "http://localhost:3000/slice-simulator",
"_latest": "0.4.2"
}

View file

@ -3,6 +3,7 @@ module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
'./slices/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {

View file

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

View file

@ -0,0 +1,228 @@
// Code generated by prismic-ts-codegen. DO NOT EDIT.
import type * as prismicT from '@prismicio/types'
import type * as prismic from '@prismicio/client'
type Simplify<T> = {
[KeyType in keyof T]: T[KeyType]
}
/** Content for Author documents */
interface AuthorDocumentData {
/**
* Name field in *Author*
*
* - **Field Type**: Title
* - **Placeholder**: Name of the author
* - **API ID Path**: author.name
* - **Tab**: Main
* - **Documentation**: https://prismic.io/docs/core-concepts/rich-text-title
*
*/
name: prismicT.TitleField
/**
* Picture field in *Author*
*
* - **Field Type**: Image
* - **Placeholder**: *None*
* - **API ID Path**: author.picture
* - **Tab**: Main
* - **Documentation**: https://prismic.io/docs/core-concepts/image
*
*/
picture: prismicT.ImageField<never>
}
/**
* Author document from Prismic
*
* - **API ID**: `author`
* - **Repeatable**: `true`
* - **Documentation**: https://prismic.io/docs/core-concepts/custom-types
*
* @typeParam Lang - Language API ID of the document.
*/
export type AuthorDocument<Lang extends string = string> =
prismicT.PrismicDocumentWithoutUID<
Simplify<AuthorDocumentData>,
'author',
Lang
>
/** Content for Post documents */
interface PostDocumentData {
/**
* Title field in *Post*
*
* - **Field Type**: Title
* - **Placeholder**: Title of the post
* - **API ID Path**: post.title
* - **Tab**: Main
* - **Documentation**: https://prismic.io/docs/core-concepts/rich-text-title
*
*/
title: prismicT.TitleField
/**
* Date field in *Post*
*
* - **Field Type**: Date
* - **Placeholder**: Date of publication
* - **API ID Path**: post.date
* - **Tab**: Main
* - **Documentation**: https://prismic.io/docs/core-concepts/date
*
*/
date: prismicT.DateField
/**
* Author field in *Post*
*
* - **Field Type**: Content Relationship
* - **Placeholder**: *None*
* - **API ID Path**: post.author
* - **Tab**: Main
* - **Documentation**: https://prismic.io/docs/core-concepts/link-content-relationship
*
*/
author: prismicT.RelationField<'author'>
/**
* Excerpt field in *Post*
*
* - **Field Type**: Text
* - **Placeholder**: Short summary of the post
* - **API ID Path**: post.excerpt
* - **Tab**: Main
* - **Documentation**: https://prismic.io/docs/core-concepts/key-text
*
*/
excerpt: prismicT.KeyTextField
/**
* Cover Image field in *Post*
*
* - **Field Type**: Image
* - **Placeholder**: *None*
* - **API ID Path**: post.cover_image
* - **Tab**: Main
* - **Documentation**: https://prismic.io/docs/core-concepts/image
*
*/
cover_image: prismicT.ImageField<never>
/**
* Slice Zone field in *Post*
*
* - **Field Type**: Slice Zone
* - **Placeholder**: *None*
* - **API ID Path**: post.slices[]
* - **Tab**: Main
* - **Documentation**: https://prismic.io/docs/core-concepts/slices
*
*/
slices: prismicT.SliceZone<PostDocumentDataSlicesSlice>
}
/**
* Slice for *Post Slice Zone*
*
*/
type PostDocumentDataSlicesSlice = TextSlice | ImageSlice
/**
* Post document from Prismic
*
* - **API ID**: `post`
* - **Repeatable**: `true`
* - **Documentation**: https://prismic.io/docs/core-concepts/custom-types
*
* @typeParam Lang - Language API ID of the document.
*/
export type PostDocument<Lang extends string = string> =
prismicT.PrismicDocumentWithUID<Simplify<PostDocumentData>, 'post', Lang>
export type AllDocumentTypes = AuthorDocument | PostDocument
/**
* Primary content in Image Primary
*
*/
interface ImageSliceDefaultPrimary {
/**
* Image field in *Image Primary*
*
* - **Field Type**: Image
* - **Placeholder**: *None*
* - **API ID Path**: image.primary.image
* - **Documentation**: https://prismic.io/docs/core-concepts/image
*
*/
image: prismicT.ImageField<never>
}
/**
* Default variation for Image Slice
*
* - **API ID**: `default`
* - **Description**: `Image`
* - **Documentation**: https://prismic.io/docs/core-concepts/reusing-slices
*
*/
export type ImageSliceDefault = prismicT.SharedSliceVariation<
'default',
Simplify<ImageSliceDefaultPrimary>,
never
>
/**
* Slice variation for *Image*
*
*/
type ImageSliceVariation = ImageSliceDefault
/**
* Image Shared Slice
*
* - **API ID**: `image`
* - **Description**: `Image`
* - **Documentation**: https://prismic.io/docs/core-concepts/reusing-slices
*
*/
export type ImageSlice = prismicT.SharedSlice<'image', ImageSliceVariation>
/**
* Primary content in Text Primary
*
*/
interface TextSliceDefaultPrimary {
/**
* Text field in *Text Primary*
*
* - **Field Type**: Rich Text
* - **Placeholder**: Rich text with formatting
* - **API ID Path**: text.primary.text
* - **Documentation**: https://prismic.io/docs/core-concepts/rich-text-title
*
*/
text: prismicT.RichTextField
}
/**
* Default variation for Text Slice
*
* - **API ID**: `default`
* - **Description**: `Text`
* - **Documentation**: https://prismic.io/docs/core-concepts/reusing-slices
*
*/
export type TextSliceDefault = prismicT.SharedSliceVariation<
'default',
Simplify<TextSliceDefaultPrimary>,
never
>
/**
* Slice variation for *Text*
*
*/
type TextSliceVariation = TextSliceDefault
/**
* Text Shared Slice
*
* - **API ID**: `text`
* - **Description**: `Text`
* - **Documentation**: https://prismic.io/docs/core-concepts/reusing-slices
*
*/
export type TextSlice = prismicT.SharedSlice<'text', TextSliceVariation>
declare module '@prismicio/client' {
interface CreateClient {
(
repositoryNameOrEndpoint: string,
options?: prismic.ClientConfig
): prismic.Client<AllDocumentTypes>
}
}

View file

@ -1,18 +0,0 @@
{
"Main": {
"name": {
"type": "Text",
"config": {
"label": "name"
}
},
"picture": {
"type": "Image",
"config": {
"constraint": {},
"thumbnails": [],
"label": "picture"
}
}
}
}

View file

@ -1,52 +0,0 @@
{
"Main": {
"title": {
"type": "StructuredText",
"config": {
"single": "heading1, heading2, heading3, heading4, heading5, heading6",
"label": "title"
}
},
"content": {
"type": "StructuredText",
"config": {
"multi": "paragraph, preformatted, heading1, heading2, heading3, heading4, heading5, heading6, strong, em, hyperlink, image, embed, list-item, o-list-item, o-list-item",
"label": "content"
}
},
"excerpt": {
"type": "Text",
"config": {
"label": "excerpt"
}
},
"coverimage": {
"type": "Image",
"config": {
"constraint": {},
"thumbnails": [],
"label": "coverimage"
}
},
"date": {
"type": "Date",
"config": {
"label": "date"
}
},
"author": {
"type": "Link",
"config": {
"select": "document",
"customtypes": ["author"],
"label": "author"
}
},
"uid": {
"type": "UID",
"config": {
"label": "slug"
}
}
}
}