chore(examples): Add 'Sitefinity CMS' Example (#39537)
This is an example of how to integrate [Sitefinity CMS ](https://www.progress.com/sitefinity-cms)with next.js Co-authored-by: Balázs Orbán <info@balazsorban.com>
2
examples/cms-sitefinity/.env.local.example
Normal file
|
@ -0,0 +1,2 @@
|
|||
SF_API_URL=
|
||||
SF_URL=
|
36
examples/cms-sitefinity/.gitignore
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
103
examples/cms-sitefinity/README.md
Normal file
|
@ -0,0 +1,103 @@
|
|||
# A statically generated blog example using Next.js and Sitefinity CMS
|
||||
|
||||
This is the existing [cms-sitefinity](https://github.com/vercel/next.js/tree/canary/examples/cms-sitefinity) plus TypeScript.
|
||||
This example showcases [Next.js's Static Generation feature](https://nextjs.org/docs/basic-features/pages) using Sitefinity CMS as the data source.
|
||||
|
||||
## Demo
|
||||
|
||||
[https://next-cms-sitefinity.vercel.app/](https://next-cms-sitefinity.vercel.app/)
|
||||
|
||||
## Deploy your own
|
||||
|
||||
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) or preview live with [StackBlitz](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/cms-sitefinity)
|
||||
|
||||
[![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-sitefinity&project-name=cms-sitefinity&repository-name=cms-sitefinity)
|
||||
|
||||
### Related examples
|
||||
|
||||
- [WordPress](/examples/cms-wordpress)
|
||||
- [DatoCMS](/examples/cms-datocms)
|
||||
- [Sanity](/examples/cms-sanity)
|
||||
- [TakeShape](/examples/cms-takeshape)
|
||||
- [Prismic](/examples/cms-prismic)
|
||||
- [Contentful](/examples/cms-contentful)
|
||||
- [Strapi](/examples/cms-strapi)
|
||||
- [Agility CMS](/examples/cms-agilitycms)
|
||||
- [Cosmic](/examples/cms-cosmic)
|
||||
- [ButterCMS](/examples/cms-buttercms)
|
||||
- [Storyblok](/examples/cms-storyblok)
|
||||
- [GraphCMS](/examples/cms-graphcms)
|
||||
- [Kontent](/examples/cms-kontent)
|
||||
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
|
||||
- [Builder.io](/examples/cms-builder-io)
|
||||
- [TinaCMS](/examples/cms-tina/)
|
||||
|
||||
## How to use
|
||||
|
||||
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:
|
||||
|
||||
```bash
|
||||
npx create-next-app --example cms-sitefinity cms-sitefinity-app
|
||||
```
|
||||
|
||||
```bash
|
||||
yarn create next-app --example cms-sitefinity cms-sitefinity-app
|
||||
```
|
||||
|
||||
```bash
|
||||
pnpm create next-app --example cms-sitefinity cms-sitefinity-app
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Step 1. Setup the CMS locally (version >=14.27922)
|
||||
|
||||
First, [install](https://www.progress.com/documentation/sitefinity-cms/install-sitefinity) and run the CMS Locally.
|
||||
|
||||
### Step 2. Import the ready to use dynamic module 'Posts'
|
||||
|
||||
For the purpose of this demo a ready to use dynamic module was build containing two types - 'Post' and 'Author'.
|
||||
|
||||
In order to install it:
|
||||
|
||||
1. Open the CMS Administration under (/Sitefinity)
|
||||
2. Open the Export/Import screen under (/Sitefinity/Administration/Packaging)
|
||||
3. Click on Import Zip file and import the file from the [sitefinity folder](./sitefinity/SitefinityExport.zip)
|
||||
|
||||
### Step 3. Enable the web service
|
||||
|
||||
By default the web services are not allowed for anonymous users, so the yhave to be enabled.
|
||||
|
||||
Go to /sitefinity/Administration/WebServices and edit the 'Default' web service to allow it to be accessible by 'Everyone'
|
||||
|
||||
### Step 4. Install the GraphQL package
|
||||
|
||||
1. Add the [Sitefinity CMS nugget source](https://www.progress.com/documentation/sitefinity-cms/sitefinity-cms-nuget-packages-repository)
|
||||
2. Install the [Progress.Sitefinity.GraphQL](https://nuget.sitefinity.com/#/package/Progress.Sitefinity.GraphQL) package (enable prerelease filter).
|
||||
|
||||
### Step 5. Set up environment variables
|
||||
|
||||
Copy the `.env.local.example` file in this directory to `.env.local` (which will be ignored by Git):
|
||||
|
||||
```bash
|
||||
cp .env.local.example .env.local
|
||||
```
|
||||
|
||||
Then set each variable on `.env.local`
|
||||
|
||||
- `SF_API_URL` - This is the url of the 'Default' web service that we configured earlier. E.g. http://locahost/api/default/
|
||||
- `SF_URL` - This is the URL of the CMS itself. E.g. http://localhost/
|
||||
|
||||
### Step 6. Run Next.js in development mode
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
|
||||
# or
|
||||
|
||||
yarn
|
||||
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).
|
48
examples/cms-sitefinity/components/alert.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
import Container from './container'
|
||||
import cn from 'classnames'
|
||||
import { EXAMPLE_PATH } from '../lib/constants'
|
||||
|
||||
type Props = {
|
||||
preview?: boolean
|
||||
}
|
||||
|
||||
const Alert = ({ preview }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className={cn('border-b', {
|
||||
'bg-neutral-800 border-neutral-800 text-white': preview,
|
||||
'bg-neutral-50 border-neutral-200': !preview,
|
||||
})}
|
||||
>
|
||||
<Container>
|
||||
<div className="py-2 text-center text-sm">
|
||||
{preview ? (
|
||||
<>
|
||||
This page is a preview.{' '}
|
||||
<a
|
||||
href="/api/exit-preview"
|
||||
className="underline hover:text-teal-300 duration-200 transition-colors"
|
||||
>
|
||||
Click here
|
||||
</a>{' '}
|
||||
to exit preview mode.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
The source code for this blog is{' '}
|
||||
<a
|
||||
href={`https://github.com/vercel/next.js/tree/canary/examples/${EXAMPLE_PATH}`}
|
||||
className="underline hover:text-blue-600 duration-200 transition-colors"
|
||||
>
|
||||
available on GitHub
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Alert
|
15
examples/cms-sitefinity/components/avatar.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
type Props = {
|
||||
name: string
|
||||
picture: string
|
||||
}
|
||||
|
||||
const Avatar = ({ name, picture }: Props) => {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<img src={picture} className="w-12 h-12 rounded-full mr-4" alt={name} />
|
||||
<div className="text-xl font-bold">{name}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Avatar
|
9
examples/cms-sitefinity/components/container.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
type Props = {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
const Container = ({ children }: Props) => {
|
||||
return <div className="container mx-auto px-5">{children}</div>
|
||||
}
|
||||
|
||||
export default Container
|
33
examples/cms-sitefinity/components/cover-image.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import cn from 'classnames'
|
||||
import Link from 'next/link'
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
src: string
|
||||
slug?: string
|
||||
}
|
||||
|
||||
const CoverImage = ({ title, src, slug }: Props) => {
|
||||
const image = (
|
||||
<img
|
||||
src={src}
|
||||
alt={`Cover Image for ${title}`}
|
||||
className={cn('shadow-sm', {
|
||||
'hover:shadow-lg transition-shadow duration-200': slug,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<div className="sm:mx-0">
|
||||
{slug ? (
|
||||
<Link as={`/posts${slug}`} href="/posts[slug]">
|
||||
<a aria-label={title}>{image}</a>
|
||||
</Link>
|
||||
) : (
|
||||
image
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CoverImage
|
12
examples/cms-sitefinity/components/date-formatter.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { parseISO, format } from 'date-fns'
|
||||
|
||||
type Props = {
|
||||
dateString: string
|
||||
}
|
||||
|
||||
const DateFormatter = ({ dateString }: Props) => {
|
||||
const date = parseISO(dateString)
|
||||
return <time dateTime={dateString}>{format(date, 'LLLL d, yyyy')}</time>
|
||||
}
|
||||
|
||||
export default DateFormatter
|
32
examples/cms-sitefinity/components/footer.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import Container from './container'
|
||||
import { EXAMPLE_PATH } from '../lib/constants'
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<footer className="bg-neutral-50 border-t border-neutral-200">
|
||||
<Container>
|
||||
<div className="py-28 flex flex-col lg:flex-row items-center">
|
||||
<h3 className="text-4xl lg:text-[2.5rem] font-bold tracking-tighter leading-tight text-center lg:text-left mb-10 lg:mb-0 lg:pr-4 lg:w-1/2">
|
||||
Statically Generated with Next.js.
|
||||
</h3>
|
||||
<div className="flex flex-col lg:flex-row justify-center items-center lg:pl-4 lg:w-1/2">
|
||||
<a
|
||||
href="https://nextjs.org/docs/basic-features/pages"
|
||||
className="mx-3 bg-black hover:bg-white hover:text-black border border-black text-white font-bold py-3 px-12 lg:px-8 duration-200 transition-colors mb-6 lg:mb-0"
|
||||
>
|
||||
Read Documentation
|
||||
</a>
|
||||
<a
|
||||
href={`https://github.com/vercel/next.js/tree/canary/examples/${EXAMPLE_PATH}`}
|
||||
className="mx-3 font-bold hover:underline"
|
||||
>
|
||||
View on GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
14
examples/cms-sitefinity/components/header.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
const Header = () => {
|
||||
return (
|
||||
<h2 className="text-2xl md:text-4xl font-bold tracking-tight md:tracking-tighter leading-tight mb-20 mt-8">
|
||||
<Link href="/">
|
||||
<a className="hover:underline">Blog</a>
|
||||
</Link>
|
||||
.
|
||||
</h2>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
49
examples/cms-sitefinity/components/hero-post.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
import Avatar from './avatar'
|
||||
import DateFormatter from './date-formatter'
|
||||
import CoverImage from './cover-image'
|
||||
import Link from 'next/link'
|
||||
import type Author from '../interfaces/author'
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
coverImage: string
|
||||
date: string
|
||||
excerpt: string
|
||||
author: Author
|
||||
slug: string
|
||||
}
|
||||
|
||||
const HeroPost = ({
|
||||
title,
|
||||
coverImage,
|
||||
date,
|
||||
excerpt,
|
||||
author,
|
||||
slug,
|
||||
}: Props) => {
|
||||
return (
|
||||
<section>
|
||||
<div className="mb-8 md:mb-16">
|
||||
<CoverImage title={title} src={coverImage} slug={slug} />
|
||||
</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-5xl leading-tight">
|
||||
<Link as={`/posts${slug}`} href="/posts[slug]">
|
||||
<a className="hover:underline">{title}</a>
|
||||
</Link>
|
||||
</h3>
|
||||
<div className="mb-4 md:mb-0 text-lg">
|
||||
<DateFormatter dateString={date} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-lg leading-relaxed mb-4">{excerpt}</p>
|
||||
<Avatar name={author.name} picture={author.picture} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default HeroPost
|
23
examples/cms-sitefinity/components/intro.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { CMS_NAME } from '../lib/constants'
|
||||
|
||||
const Intro = () => {
|
||||
return (
|
||||
<section className="flex-col md:flex-row flex items-center md:justify-between mt-16 mb-16 md:mb-12">
|
||||
<h1 className="text-5xl md:text-8xl font-bold tracking-tighter leading-tight md:pr-8">
|
||||
Blog.
|
||||
</h1>
|
||||
<h4 className="text-center md:text-left text-lg mt-5 md:pl-8">
|
||||
A statically generated blog example using{' '}
|
||||
<a
|
||||
href="https://nextjs.org/"
|
||||
className="underline hover:text-blue-600 duration-200 transition-colors"
|
||||
>
|
||||
Next.js
|
||||
</a>{' '}
|
||||
and {CMS_NAME}.
|
||||
</h4>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default Intro
|
23
examples/cms-sitefinity/components/layout.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import Alert from './alert'
|
||||
import Footer from './footer'
|
||||
import Meta from './meta'
|
||||
|
||||
type Props = {
|
||||
preview?: boolean
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const Layout = ({ preview, children }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<Meta />
|
||||
<div className="min-h-screen">
|
||||
<Alert preview={preview} />
|
||||
<main>{children}</main>
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Layout
|
|
@ -0,0 +1,18 @@
|
|||
.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;
|
||||
}
|
44
examples/cms-sitefinity/components/meta.tsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
import Head from 'next/head'
|
||||
import { CMS_NAME, HOME_OG_IMAGE_URL } from '../lib/constants'
|
||||
|
||||
const Meta = () => {
|
||||
return (
|
||||
<Head>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/favicon/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/favicon/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/favicon/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/favicon/site.webmanifest" />
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="/favicon/safari-pinned-tab.svg"
|
||||
color="#000000"
|
||||
/>
|
||||
<link rel="shortcut icon" href="/favicon/favicon.ico" />
|
||||
<meta name="msapplication-TileColor" content="#000000" />
|
||||
<meta name="msapplication-config" content="/favicon/browserconfig.xml" />
|
||||
<meta name="theme-color" content="#000" />
|
||||
<link rel="alternate" type="application/rss+xml" href="/feed.xml" />
|
||||
<meta
|
||||
name="description"
|
||||
content={`A statically generated blog example using Next.js and ${CMS_NAME}.`}
|
||||
/>
|
||||
<meta property="og:image" content={HOME_OG_IMAGE_URL} />
|
||||
</Head>
|
||||
)
|
||||
}
|
||||
|
||||
export default Meta
|
31
examples/cms-sitefinity/components/more-stories.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import PostPreview from './post-preview'
|
||||
import type Post from '../interfaces/post'
|
||||
|
||||
type Props = {
|
||||
posts: Post[]
|
||||
}
|
||||
|
||||
const MoreStories = ({ posts }: Props) => {
|
||||
return (
|
||||
<section>
|
||||
<h2 className="mb-8 text-5xl 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.slug}
|
||||
title={post.title}
|
||||
coverImage={post.coverImage}
|
||||
date={post.date}
|
||||
author={post.author}
|
||||
slug={post.slug}
|
||||
excerpt={post.excerpt}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default MoreStories
|
18
examples/cms-sitefinity/components/post-body.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import markdownStyles from './markdown-styles.module.css'
|
||||
|
||||
type Props = {
|
||||
content: string
|
||||
}
|
||||
|
||||
const PostBody = ({ content }: Props) => {
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<div
|
||||
className={markdownStyles['markdown']}
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PostBody
|
36
examples/cms-sitefinity/components/post-header.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import Avatar from './avatar'
|
||||
import DateFormatter from './date-formatter'
|
||||
import CoverImage from './cover-image'
|
||||
import PostTitle from './post-title'
|
||||
import type Author from '../interfaces/author'
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
coverImage: string
|
||||
date: string
|
||||
author: Author
|
||||
}
|
||||
|
||||
const PostHeader = ({ title, coverImage, date, author }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<PostTitle>{title}</PostTitle>
|
||||
<div className="hidden md:block md:mb-12">
|
||||
<Avatar name={author.name} picture={author.picture} />
|
||||
</div>
|
||||
<div className="mb-8 md:mb-16 sm:mx-0">
|
||||
<CoverImage title={title} src={coverImage} />
|
||||
</div>
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<div className="block md:hidden mb-6">
|
||||
<Avatar name={author.name} picture={author.picture} />
|
||||
</div>
|
||||
<div className="mb-6 text-lg">
|
||||
<DateFormatter dateString={date} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PostHeader
|
43
examples/cms-sitefinity/components/post-preview.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
import Avatar from './avatar'
|
||||
import DateFormatter from './date-formatter'
|
||||
import CoverImage from './cover-image'
|
||||
import Link from 'next/link'
|
||||
import type Author from '../interfaces/author'
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
coverImage: string
|
||||
date: string
|
||||
excerpt: string
|
||||
author: Author
|
||||
slug: string
|
||||
}
|
||||
|
||||
const PostPreview = ({
|
||||
title,
|
||||
coverImage,
|
||||
date,
|
||||
excerpt,
|
||||
author,
|
||||
slug,
|
||||
}: Props) => {
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-5">
|
||||
<CoverImage slug={slug} title={title} src={coverImage} />
|
||||
</div>
|
||||
<h3 className="text-3xl mb-3 leading-snug">
|
||||
<Link as={`/posts${slug}`} href="/posts[slug]">
|
||||
<a className="hover:underline">{title}</a>
|
||||
</Link>
|
||||
</h3>
|
||||
<div className="text-lg mb-4">
|
||||
<DateFormatter dateString={date} />
|
||||
</div>
|
||||
<p className="text-lg leading-relaxed mb-4">{excerpt}</p>
|
||||
<Avatar name={author.name} picture={author.picture} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PostPreview
|
15
examples/cms-sitefinity/components/post-title.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { ReactNode } from 'react'
|
||||
|
||||
type Props = {
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
const PostTitle = ({ children }: Props) => {
|
||||
return (
|
||||
<h1 className="text-5xl md:text-7xl lg:text-8xl font-bold tracking-tighter leading-tight md:leading-none mb-12 text-center md:text-left">
|
||||
{children}
|
||||
</h1>
|
||||
)
|
||||
}
|
||||
|
||||
export default PostTitle
|
5
examples/cms-sitefinity/components/section-separator.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
const SectionSeparator = () => {
|
||||
return <hr className="border-neutral-200 mt-28 mb-24" />
|
||||
}
|
||||
|
||||
export default SectionSeparator
|
6
examples/cms-sitefinity/interfaces/author.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
type Author = {
|
||||
name: string
|
||||
picture: string
|
||||
}
|
||||
|
||||
export default Author
|
16
examples/cms-sitefinity/interfaces/post.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import type Author from './author'
|
||||
|
||||
type PostType = {
|
||||
slug: string
|
||||
title: string
|
||||
date: string
|
||||
coverImage: string
|
||||
author: Author
|
||||
excerpt: string
|
||||
ogImage: {
|
||||
url: string
|
||||
}
|
||||
content: string
|
||||
}
|
||||
|
||||
export default PostType
|
140
examples/cms-sitefinity/lib/api.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
import PostType from '../interfaces/post'
|
||||
|
||||
export async function executeGraphQLForBlogPosts(
|
||||
query: string
|
||||
): Promise<CmsPost[]> {
|
||||
const graphQLEndpoint = `${process.env.SF_API_URL}graphql`
|
||||
const response = await fetch(graphQLEndpoint, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ query }),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}).then((x) => x.json())
|
||||
return response['data']['posts']
|
||||
}
|
||||
|
||||
export async function getAllPostSlugsFromCms(): Promise<string[]> {
|
||||
var query = `
|
||||
query {
|
||||
posts {
|
||||
itemDefaultUrl
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const blogPosts = await executeGraphQLForBlogPosts(query);
|
||||
const slugs = blogPosts.map((x) => x.itemDefaultUrl);
|
||||
return slugs;
|
||||
}
|
||||
|
||||
function transformImageUrl(url: string) {
|
||||
if (!url.startsWith('http')) {
|
||||
url = process.env.SF_URL + url.substring(1)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
function mapCmsBlog(source: CmsPost): PostType {
|
||||
return {
|
||||
content: source.content,
|
||||
excerpt: source.excerpt,
|
||||
date: source.dateCreated,
|
||||
slug: source.itemDefaultUrl,
|
||||
title: source.title,
|
||||
author: {
|
||||
name: source.authorOfPost[0].title,
|
||||
picture: transformImageUrl(source.authorOfPost[0].picture[0].url),
|
||||
},
|
||||
coverImage: transformImageUrl(source.coverImage[0].url),
|
||||
ogImage: {
|
||||
url: transformImageUrl(source.openGraphImage[0].url),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPostBySlugFromCms(slug: string): Promise<PostType> {
|
||||
const modifiedSlug = slug
|
||||
var query = `
|
||||
query {
|
||||
posts(_filter: {itemDefaultUrl: {_eq: "${modifiedSlug}"}}) {
|
||||
id
|
||||
title
|
||||
excerpt
|
||||
content
|
||||
dateCreated
|
||||
itemDefaultUrl
|
||||
openGraphImage {
|
||||
url
|
||||
}
|
||||
coverImage {
|
||||
url
|
||||
}
|
||||
authorOfPost {
|
||||
title
|
||||
picture {
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const blogPosts = (await executeGraphQLForBlogPosts(query)).map((x) =>
|
||||
mapCmsBlog(x)
|
||||
)
|
||||
if (blogPosts.length > 0) return blogPosts[0]
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export async function getAllPostsFromCms(): Promise<PostType[]> {
|
||||
var query = `
|
||||
query {
|
||||
posts {
|
||||
id
|
||||
title
|
||||
excerpt
|
||||
content
|
||||
dateCreated
|
||||
itemDefaultUrl
|
||||
openGraphImage {
|
||||
url
|
||||
}
|
||||
coverImage {
|
||||
url
|
||||
}
|
||||
authorOfPost {
|
||||
title
|
||||
picture {
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const blogPosts = (await executeGraphQLForBlogPosts(query)).map((x) =>
|
||||
mapCmsBlog(x)
|
||||
)
|
||||
return blogPosts
|
||||
}
|
||||
|
||||
interface CmsPost {
|
||||
title: string
|
||||
excerpt: string
|
||||
dateCreated: string
|
||||
content: string
|
||||
itemDefaultUrl: string
|
||||
openGraphImage: {
|
||||
url: string
|
||||
}
|
||||
coverImage: {
|
||||
url: string
|
||||
}
|
||||
authorOfPost: {
|
||||
title: string
|
||||
picture: {
|
||||
url: string
|
||||
}
|
||||
}
|
||||
}
|
4
examples/cms-sitefinity/lib/constants.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export const EXAMPLE_PATH = 'blog-starter'
|
||||
export const CMS_NAME = 'Sitefinity'
|
||||
export const HOME_OG_IMAGE_URL =
|
||||
'https://og-image.vercel.app/Next.js%20Blog%20Starter%20Example.png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg'
|
24
examples/cms-sitefinity/package.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.3.1",
|
||||
"date-fns": "^2.28.0",
|
||||
"next": "latest",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.0.3",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"postcss": "^8.4.14",
|
||||
"tailwindcss": "^3.1.4",
|
||||
"typescript": "^4.7.4"
|
||||
}
|
||||
}
|
6
examples/cms-sitefinity/pages/_app.tsx
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { AppProps } from 'next/app'
|
||||
import '../styles/index.css'
|
||||
|
||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
49
examples/cms-sitefinity/pages/index.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
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 { getAllPostsFromCms, getAllPostSlugsFromCms } from '../lib/api'
|
||||
import Head from 'next/head'
|
||||
import { CMS_NAME } from '../lib/constants'
|
||||
import Post from '../interfaces/post'
|
||||
|
||||
type Props = {
|
||||
allPosts: Post[]
|
||||
}
|
||||
|
||||
export default function Index({ allPosts }: Props) {
|
||||
const heroPost = allPosts[0]
|
||||
const morePosts = allPosts.slice(1)
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<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.slug}
|
||||
excerpt={heroPost.excerpt}
|
||||
/>
|
||||
)}
|
||||
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
|
||||
</Container>
|
||||
</Layout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const getStaticProps = async () => {
|
||||
const allPosts = await getAllPostsFromCms()
|
||||
|
||||
return {
|
||||
props: { allPosts },
|
||||
}
|
||||
}
|
85
examples/cms-sitefinity/pages/posts/[...slug].tsx
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { useRouter } from 'next/router'
|
||||
import ErrorPage from 'next/error'
|
||||
import Container from '../../components/container'
|
||||
import PostBody from '../../components/post-body'
|
||||
import Header from '../../components/header'
|
||||
import PostHeader from '../../components/post-header'
|
||||
import Layout from '../../components/layout'
|
||||
import { getAllPostSlugsFromCms, getPostBySlugFromCms } from '../../lib/api'
|
||||
import PostTitle from '../../components/post-title'
|
||||
import Head from 'next/head'
|
||||
import { CMS_NAME } from '../../lib/constants'
|
||||
import type PostType from '../../interfaces/post'
|
||||
|
||||
type Props = {
|
||||
post: PostType
|
||||
morePosts: PostType[]
|
||||
preview?: boolean
|
||||
}
|
||||
|
||||
export default function Post({ post, morePosts, preview }: Props) {
|
||||
const router = useRouter()
|
||||
if (!router.isFallback && !post?.slug) {
|
||||
return <ErrorPage statusCode={404} />
|
||||
}
|
||||
return (
|
||||
<Layout preview={preview}>
|
||||
<Container>
|
||||
<Header />
|
||||
{router.isFallback ? (
|
||||
<PostTitle>Loading…</PostTitle>
|
||||
) : (
|
||||
<>
|
||||
<article className="mb-32">
|
||||
<Head>
|
||||
<title>
|
||||
{post.title} | Next.js Blog Example with {CMS_NAME}
|
||||
</title>
|
||||
<meta property="og:image" content={post.ogImage.url} />
|
||||
</Head>
|
||||
<PostHeader
|
||||
title={post.title}
|
||||
coverImage={post.coverImage}
|
||||
date={post.date}
|
||||
author={post.author}
|
||||
/>
|
||||
<PostBody content={post.content} />
|
||||
</article>
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
type Params = {
|
||||
params: {
|
||||
slug: string[]
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params }: Params) {
|
||||
debugger
|
||||
const post = await getPostBySlugFromCms('/' + params.slug.join('/'))
|
||||
|
||||
return {
|
||||
props: {
|
||||
post,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const slugs = await getAllPostSlugsFromCms()
|
||||
|
||||
return {
|
||||
paths: slugs.map((slug) => {
|
||||
return {
|
||||
params: {
|
||||
slug: slug.split('/').splice(1),
|
||||
},
|
||||
}
|
||||
}),
|
||||
fallback: false,
|
||||
}
|
||||
}
|
8
examples/cms-sitefinity/postcss.config.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
// If you want to use other PostCSS plugins, see the following:
|
||||
// https://tailwindcss.com/docs/using-with-preprocessors
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 14 KiB |
BIN
examples/cms-sitefinity/public/favicon/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
9
examples/cms-sitefinity/public/favicon/browserconfig.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/favicons/mstile-150x150.png"/>
|
||||
<TileColor>#000000</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
BIN
examples/cms-sitefinity/public/favicon/favicon-16x16.png
Normal file
After Width: | Height: | Size: 595 B |
BIN
examples/cms-sitefinity/public/favicon/favicon-32x32.png
Normal file
After Width: | Height: | Size: 880 B |
BIN
examples/cms-sitefinity/public/favicon/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
examples/cms-sitefinity/public/favicon/mstile-150x150.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
33
examples/cms-sitefinity/public/favicon/safari-pinned-tab.svg
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M4785 10234 c-22 -2 -92 -9 -155 -14 -1453 -131 -2814 -915 -3676
|
||||
-2120 -480 -670 -787 -1430 -903 -2235 -41 -281 -46 -364 -46 -745 0 -381 5
|
||||
-464 46 -745 278 -1921 1645 -3535 3499 -4133 332 -107 682 -180 1080 -224
|
||||
155 -17 825 -17 980 0 687 76 1269 246 1843 539 88 45 105 57 93 67 -8 6 -383
|
||||
509 -833 1117 l-818 1105 -1025 1517 c-564 834 -1028 1516 -1032 1516 -4 1 -8
|
||||
-673 -10 -1496 -3 -1441 -4 -1499 -22 -1533 -26 -49 -46 -69 -88 -91 -32 -16
|
||||
-60 -19 -211 -19 l-173 0 -46 29 c-30 19 -52 44 -67 73 l-21 45 2 2005 3 2006
|
||||
31 39 c16 21 50 48 74 61 41 20 57 22 230 22 204 0 238 -8 291 -66 15 -16 570
|
||||
-852 1234 -1859 664 -1007 1572 -2382 2018 -3057 l810 -1227 41 27 c363 236
|
||||
747 572 1051 922 647 743 1064 1649 1204 2615 41 281 46 364 46 745 0 381 -5
|
||||
464 -46 745 -278 1921 -1645 3535 -3499 4133 -327 106 -675 179 -1065 223 -96
|
||||
10 -757 21 -840 13z m2094 -3094 c48 -24 87 -70 101 -118 8 -26 10 -582 8
|
||||
-1835 l-3 -1798 -317 486 -318 486 0 1307 c0 845 4 1320 10 1343 16 56 51 100
|
||||
99 126 41 21 56 23 213 23 148 0 174 -2 207 -20z"/>
|
||||
<path d="M7843 789 c-35 -22 -46 -37 -15 -20 22 13 58 40 52 41 -3 0 -20 -10
|
||||
-37 -21z"/>
|
||||
<path d="M7774 744 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
|
||||
<path d="M7724 714 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
|
||||
<path d="M7674 684 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
|
||||
<path d="M7598 644 c-38 -20 -36 -28 2 -9 17 9 30 18 30 20 0 7 -1 6 -32 -11z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
19
examples/cms-sitefinity/public/favicon/site.webmanifest
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "Next.js",
|
||||
"short_name": "Next.js",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#000000",
|
||||
"display": "standalone"
|
||||
}
|
BIN
examples/cms-sitefinity/sitefinity/SitefinityExport.zip
Normal file
3
examples/cms-sitefinity/styles/index.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
34
examples/cms-sitefinity/tailwind.config.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
module.exports = {
|
||||
content: ['./components/**/*.tsx', './pages/**/*.tsx'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'accent-1': '#FAFAFA',
|
||||
'accent-2': '#EAEAEA',
|
||||
'accent-7': '#333',
|
||||
success: '#0070f3',
|
||||
cyan: '#79FFE1',
|
||||
},
|
||||
spacing: {
|
||||
28: '7rem',
|
||||
},
|
||||
letterSpacing: {
|
||||
tighter: '-.04em',
|
||||
},
|
||||
lineHeight: {
|
||||
tight: 1.2,
|
||||
},
|
||||
fontSize: {
|
||||
'5xl': '2.5rem',
|
||||
'6xl': '2.75rem',
|
||||
'7xl': '4.5rem',
|
||||
'8xl': '6.25rem',
|
||||
},
|
||||
boxShadow: {
|
||||
sm: '0 5px 10px rgba(0, 0, 0, 0.12)',
|
||||
md: '0 8px 30px rgba(0, 0, 0, 0.12)',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
20
examples/cms-sitefinity/tsconfig.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"jsx": "preserve",
|
||||
"strict": false,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"noEmit": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"incremental": true
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|