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:
parent
cfd58ae80e
commit
3ede313e1f
71 changed files with 1490 additions and 700 deletions
|
@ -1,3 +0,0 @@
|
|||
PRISMIC_API_TOKEN=
|
||||
PRISMIC_REPOSITORY_NAME=
|
||||
PRISMIC_REPOSITORY_LOCALE="en-us"
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 |
|
@ -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 = ''
|
|
@ -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 |
|
@ -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 = ''
|
|
@ -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"
|
||||
}
|
||||
]
|
140
examples/cms-prismic/.slicemachine/libraries-state.json
Normal file
140
examples/cms-prismic/.slicemachine/libraries-state.json
Normal 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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
examples/cms-prismic/.slicemachine/mock-config.json
Normal file
43
examples/cms-prismic/.slicemachine/mock-config.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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', {
|
|
@ -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>
|
||||
)
|
||||
}
|
22
examples/cms-prismic/components/avatar.tsx
Normal file
22
examples/cms-prismic/components/avatar.tsx
Normal 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>
|
||||
)
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export default function Container({ children }) {
|
||||
return <div className="container mx-auto px-5">{children}</div>
|
||||
}
|
7
examples/cms-prismic/components/container.tsx
Normal file
7
examples/cms-prismic/components/container.tsx
Normal 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>
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
40
examples/cms-prismic/components/cover-image.tsx
Normal file
40
examples/cms-prismic/components/cover-image.tsx
Normal 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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
}
|
18
examples/cms-prismic/components/date.tsx
Normal file
18
examples/cms-prismic/components/date.tsx
Normal 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>
|
||||
}
|
|
@ -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">
|
|
@ -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>
|
||||
)
|
||||
}
|
59
examples/cms-prismic/components/hero-post.tsx
Normal file
59
examples/cms-prismic/components/hero-post.tsx
Normal 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>
|
||||
)
|
||||
}
|
|
@ -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 />
|
|
@ -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;
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import Head from 'next/head'
|
||||
|
||||
import { CMS_NAME, HOME_OG_IMAGE_URL } from '../lib/constants'
|
||||
|
||||
export default function Meta() {
|
|
@ -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>
|
||||
)
|
||||
}
|
30
examples/cms-prismic/components/more-stories.tsx
Normal file
30
examples/cms-prismic/components/more-stories.tsx
Normal 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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
16
examples/cms-prismic/components/post-body.tsx
Normal file
16
examples/cms-prismic/components/post-body.tsx
Normal 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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
56
examples/cms-prismic/components/post-header.tsx
Normal file
56
examples/cms-prismic/components/post-header.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
51
examples/cms-prismic/components/post-preview.tsx
Normal file
51
examples/cms-prismic/components/post-preview.tsx
Normal 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>
|
||||
)
|
||||
}
|
|
@ -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}
|
27
examples/cms-prismic/customtypes/author/index.json
Normal file
27
examples/cms-prismic/customtypes/author/index.json
Normal 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": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
70
examples/cms-prismic/customtypes/post/index.json
Normal file
70
examples/cms-prismic/customtypes/post/index.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
examples/cms-prismic/documents/.keep
Normal file
1
examples/cms-prismic/documents/.keep
Normal file
|
@ -0,0 +1 @@
|
|||
This directory can be removed after running `npx @slicemachine/init`.
|
|
@ -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
|
||||
}
|
40
examples/cms-prismic/lib/prismic.ts
Normal file
40
examples/cms-prismic/lib/prismic.ts
Normal 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
|
||||
}
|
17
examples/cms-prismic/lib/types.ts
Normal file
17
examples/cms-prismic/lib/types.ts
Normal 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
|
||||
}
|
||||
>
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
import '../styles/index.css'
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
|
||||
export default MyApp
|
13
examples/cms-prismic/pages/_app.tsx
Normal file
13
examples/cms-prismic/pages/_app.tsx
Normal 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
|
|
@ -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()
|
||||
}
|
6
examples/cms-prismic/pages/api/exit-preview.ts
Normal file
6
examples/cms-prismic/pages/api/exit-preview.ts
Normal 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 })
|
||||
}
|
|
@ -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()
|
||||
}
|
15
examples/cms-prismic/pages/api/preview.ts
Normal file
15
examples/cms-prismic/pages/api/preview.ts
Normal 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 })
|
||||
}
|
|
@ -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 },
|
||||
}
|
||||
}
|
59
examples/cms-prismic/pages/index.tsx
Normal file
59
examples/cms-prismic/pages/index.tsx
Normal 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 },
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
112
examples/cms-prismic/pages/posts/[slug].tsx
Normal file
112
examples/cms-prismic/pages/posts/[slug].tsx
Normal 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,
|
||||
}
|
||||
}
|
14
examples/cms-prismic/pages/slice-simulator.tsx
Normal file
14
examples/cms-prismic/pages/slice-simulator.tsx
Normal 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}
|
||||
/>
|
||||
)
|
||||
}
|
8
examples/cms-prismic/prismicCodegen.config.ts
Normal file
8
examples/cms-prismic/prismicCodegen.config.ts
Normal 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
|
15
examples/cms-prismic/slices/Image/index.tsx
Normal file
15
examples/cms-prismic/slices/Image/index.tsx
Normal 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
|
27
examples/cms-prismic/slices/Image/model.json
Normal file
27
examples/cms-prismic/slices/Image/model.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
27
examples/cms-prismic/slices/Text/index.tsx
Normal file
27
examples/cms-prismic/slices/Text/index.tsx
Normal 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
|
28
examples/cms-prismic/slices/Text/model.json
Normal file
28
examples/cms-prismic/slices/Text/model.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
11
examples/cms-prismic/slices/index.js
Normal file
11
examples/cms-prismic/slices/index.js
Normal 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,
|
||||
}
|
6
examples/cms-prismic/sm.json
Normal file
6
examples/cms-prismic/sm.json
Normal 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"
|
||||
}
|
|
@ -3,6 +3,7 @@ module.exports = {
|
|||
content: [
|
||||
'./pages/**/*.{js,ts,jsx,tsx}',
|
||||
'./components/**/*.{js,ts,jsx,tsx}',
|
||||
'./slices/**/*.{js,ts,jsx,tsx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
|
|
20
examples/cms-prismic/tsconfig.json
Normal file
20
examples/cms-prismic/tsconfig.json
Normal 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"]
|
||||
}
|
228
examples/cms-prismic/types.generated.ts
Normal file
228
examples/cms-prismic/types.generated.ts
Normal 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>
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"Main": {
|
||||
"name": {
|
||||
"type": "Text",
|
||||
"config": {
|
||||
"label": "name"
|
||||
}
|
||||
},
|
||||
"picture": {
|
||||
"type": "Image",
|
||||
"config": {
|
||||
"constraint": {},
|
||||
"thumbnails": [],
|
||||
"label": "picture"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue