├── next.config.js
├── public
├── favicon.ico
└── vercel.svg
├── postcss.config.js
├── .eslintrc.json
├── tailwind.config.js
├── pages
├── _app.js
├── api
│ └── comments.js
├── index.js
├── category
│ └── [slug].js
└── post
│ └── [slug].js
├── components
├── Layout.jsx
├── index.js
├── Author.jsx
├── Categories.jsx
├── Loader.jsx
├── Header.jsx
├── Comments.jsx
├── FeaturedPostCard.jsx
├── PostWidget.jsx
├── PostCard.jsx
├── SinglePost.jsx
├── PostDetail.jsx
└── CommentsForm.jsx
├── .gitignore
├── package.json
├── styles
└── globals.scss
├── README.md
├── sections
└── FeaturedPosts.jsx
└── services
└── index.js
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | reactStrictMode: true,
3 | }
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SameerBadriddinov/NextJS-GraphQL.-CMS-Blog/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals",
3 | "rules": {
4 | "react/jsx-no-duplicate-props": [1, { "ignoreCase": false }]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: [
3 | "./pages/**/*.{js,ts,jsx,tsx}",
4 | "./components/**/*.{js,ts,jsx,tsx}",
5 | ],
6 | theme: {
7 | extend: {},
8 | },
9 | plugins: [],
10 | }
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import 'tailwindcss/tailwind.css'
2 | import '../styles/globals.scss'
3 | import {Layout} from '../components'
4 |
5 | function MyApp({ Component, pageProps }) {
6 | return (
7 |
8 |
9 |
10 | )
11 | }
12 |
13 | export default MyApp
14 |
--------------------------------------------------------------------------------
/components/Layout.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Header } from '.';
3 | import FeaturedPosts from '../sections/FeaturedPosts';
4 |
5 | const Layout = ({children}) => {
6 | return (
7 | <>
8 |
9 |
10 | {children}
11 | >
12 | )
13 | };
14 |
15 | export default Layout;
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env
29 | .env.local
30 | .env.development.local
31 | .env.test.local
32 | .env.production.local
33 |
34 | # vercel
35 | .vercel
36 |
--------------------------------------------------------------------------------
/components/index.js:
--------------------------------------------------------------------------------
1 | export {default as PostCard} from './PostCard'
2 | export {default as PostWidget} from './PostWidget'
3 | export {default as Categories} from './Categories'
4 | export {default as Header} from './Header'
5 | export {default as Layout} from './Layout'
6 | export {default as PostDetail} from './PostDetail'
7 | export {default as Author} from './Author'
8 | export {default as CommentsForm} from './CommentsForm'
9 | export {default as Comments} from './Comments'
10 | export {default as Loader} from './Loader'
11 | export {default as SinglePost} from './SinglePost'
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cms-blog",
3 | "private": true,
4 | "scripts": {
5 | "dev": "next dev",
6 | "build": "next build",
7 | "start": "next start",
8 | "lint": "next lint"
9 | },
10 | "dependencies": {
11 | "graphql": "^16.2.0",
12 | "graphql-request": "^3.7.0",
13 | "html-react-parser": "^1.4.5",
14 | "moment": "^2.29.1",
15 | "next": "12.0.8",
16 | "react": "17.0.2",
17 | "react-dom": "17.0.2",
18 | "react-multi-carousel": "^2.6.5",
19 | "sass": "^1.49.0"
20 | },
21 | "devDependencies": {
22 | "autoprefixer": "^10.4.2",
23 | "eslint": "8.7.0",
24 | "eslint-config-next": "12.0.8",
25 | "postcss": "^8.4.5",
26 | "tailwindcss": "^3.0.15"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/components/Author.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Image from 'next/image';
3 |
4 | const Author = ({author}) => {
5 | return (
6 |
7 |
8 |
16 |
17 |
{author.name}
18 |
{author.bio}
19 |
20 | )
21 | };
22 |
23 | export default Author;
24 |
--------------------------------------------------------------------------------
/pages/api/comments.js:
--------------------------------------------------------------------------------
1 | import { GraphQLClient, gql } from "graphql-request";
2 |
3 | const graphqlAPI = process.env.NEXT_PUBLIC_GRAPHCMS_ENDPOINT
4 | const graphToken = process.env.GRAPHCMS_TOKEN
5 |
6 | export default async function comments(req, res) {
7 | const graphQLClient = new GraphQLClient(graphqlAPI, {
8 | headers: {
9 | authorization: `Bearer ${graphToken}`
10 | }
11 | })
12 |
13 | const query = gql`
14 | mutation CreateComment($name: String!, $email: String!, $comment: String!, $slug: String!) {
15 | createComment(data: {name: $name, email: $email, comment: $comment, post: {connect: {slug: $slug}}}){id}
16 | }
17 | `
18 |
19 | try{
20 | const result = await graphQLClient.request(query, req.body)
21 | return res.status(200).send(result)
22 | }catch(e) {
23 | console.log(e)
24 | }
25 | }
--------------------------------------------------------------------------------
/components/Categories.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Link from 'next/link';
3 | import { getCategories } from '../services';
4 |
5 | const Categories = () => {
6 | const [categories, setCategories] = useState([])
7 |
8 | useEffect(() => {
9 | getCategories()
10 | .then(res => setCategories(res))
11 | }, [])
12 |
13 | return (
14 |
15 |
16 | Categories
17 |
18 | {categories.map(category => (
19 |
20 |
21 | {category.name}
22 |
23 |
24 | ))}
25 |
26 | )
27 | };
28 |
29 | export default Categories;
30 |
--------------------------------------------------------------------------------
/components/Loader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Loader = () => (
4 |
5 |
10 |
11 |
12 |
13 |
14 | Loading
15 |
16 |
17 | );
18 |
19 | export default Loader;
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Link from 'next/link';
3 | import { getCategories } from '../services';
4 |
5 | const Header = () => {
6 | const [categories, setCategories] = useState([])
7 |
8 | useEffect(() => {
9 | getCategories()
10 | .then(res => setCategories(res))
11 | }, [])
12 |
13 | return (
14 |
15 |
16 |
17 |
18 |
19 | Blog CMS
20 |
21 |
22 |
23 |
24 | {categories.map(category => (
25 |
26 |
27 | {category.name}
28 |
29 |
30 | ))}
31 |
32 |
33 |
34 | )
35 | };
36 |
37 | export default Header;
38 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import {Categories, PostCard, PostWidget} from '../components'
3 | import {getPosts} from '../services'
4 |
5 | import FeaturedPosts from '../sections/FeaturedPosts'
6 |
7 | export default function Home({posts}) {
8 | return (
9 |
10 |
11 |
Create Next App
12 |
13 |
14 |
15 |
16 |
24 |
25 | {posts.map((post, index) =>
)}
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | export async function getStaticProps() {
33 | const posts = (await getPosts()) || [];
34 |
35 | return{
36 | props: {posts}
37 | }
38 | }
--------------------------------------------------------------------------------
/styles/globals.scss:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;700&display=swap");
6 |
7 | html,
8 | body {
9 | padding: 0;
10 | margin: 0;
11 | font-family: "Montserrat", sans-serif;
12 | &:before {
13 | content: "";
14 | content: "";
15 | width: 100%;
16 | height: 100vh;
17 | background-color: #000000;
18 | background-image: linear-gradient(147deg, #000000 0%, #04619f 74%);
19 | position: fixed;
20 | left: 0;
21 | top: 0;
22 | z-index: -1;
23 | background-position: 50% 50%;
24 | background-repeat: no-repeat;
25 | background-size: cover;
26 | }
27 | }
28 |
29 | .text-shadow {
30 | text-shadow: 0px 2px 0px rgb(0 0 0 / 30%);
31 | }
32 |
33 | .adjacent-post {
34 | & .arrow-btn {
35 | transition: width 300ms ease;
36 | width: 50px;
37 | }
38 | &:hover {
39 | & .arrow-btn {
40 | width: 50px;
41 | }
42 | }
43 | }
44 |
45 | .react-multi-carousel-list {
46 | & .arrow-btn {
47 | transition: width 300ms ease;
48 | width: 50px;
49 | &:hover {
50 | width: 60px;
51 | }
52 | }
53 | }
54 |
55 | a {
56 | color: inherit;
57 | text-decoration: none;
58 | }
59 |
60 | * {
61 | box-sizing: border-box;
62 | }
63 |
--------------------------------------------------------------------------------
/components/Comments.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from 'react';
2 | import moment from 'moment'
3 | import parse from 'html-react-parser'
4 | import { getComments } from '../services';
5 |
6 | const Comments = ({slug}) => {
7 | const [comments, setComments] = useState([])
8 |
9 | useEffect(() => {
10 | getComments(slug)
11 | .then(res => setComments(res))
12 | }, [])
13 |
14 | return (
15 | <>
16 | {comments.length > 0 && (
17 |
18 |
19 | {comments.length}
20 | {" "}
21 | Comments
22 |
23 | {comments.map((comment, index) => (
24 |
25 |
26 | {comment.name}
27 | {" "}
28 | on
29 | {" "}
30 | {moment(comment.createdAt).format("MMM DD, YYYY")}
31 |
32 |
{parse(comment.comment)}
33 |
34 | ))}
35 |
36 | )}
37 | >
38 | )
39 | };
40 |
41 | export default Comments;
42 |
--------------------------------------------------------------------------------
/pages/category/[slug].js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useRouter } from 'next/router';
3 |
4 | import {PostCard, Categories, Loader} from '../../components'
5 | import { getCategories, getCategoryPost } from '../../services';
6 |
7 | const Category = ({posts}) => {
8 | const router = useRouter()
9 |
10 | if(router.isFallback) {
11 | return
12 | }
13 |
14 | return (
15 |
16 |
17 |
22 |
23 | {posts.map((post, index) => (
24 |
25 | ))}
26 |
27 |
28 |
29 | )
30 | };
31 |
32 | export default Category;
33 |
34 | export async function getStaticProps({params}) {
35 | const posts = await getCategoryPost(params.slug)
36 |
37 | return {
38 | props: {posts}
39 | }
40 | }
41 |
42 | export async function getStaticPaths() {
43 | const categories = await getCategories()
44 | return {
45 | paths: categories.map(({slug}) => ({params: {slug}})),
46 | fallback: true
47 | }
48 | }
--------------------------------------------------------------------------------
/components/FeaturedPostCard.jsx:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 | import Image from 'next/image';
3 | import Link from 'next/link';
4 | import React from 'react';
5 |
6 | const FeaturedPostCard = ({post}) => {
7 | return (
8 |
9 |
10 |
11 |
12 |
{moment(post.createdAt).format('MMM DD, YYYY')}
13 |
{post.title}
14 |
15 |
23 |
{post.author.name}
24 |
25 |
26 |
27 |
28 | )
29 | };
30 |
31 | export default FeaturedPostCard;
32 |
--------------------------------------------------------------------------------
/pages/post/[slug].js:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router';
2 | import React from 'react';
3 |
4 | import {Author, Comments, Categories, CommentsForm, Loader, PostDetail, PostWidget, SinglePost} from '../../components';
5 | import { getPostDetails, getPosts } from '../../services';
6 |
7 | const PostDetails = ({post}) => {
8 | const router = useRouter()
9 |
10 | if(router.isFallback) {
11 | return
12 | }
13 |
14 | return (
15 |
16 |
17 |
18 |
19 |
category.slug)} />
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
category.slug)} />
29 |
30 |
31 |
32 | )
33 | };
34 |
35 | export default PostDetails;
36 |
37 | export async function getStaticProps({params}) {
38 | const data = await getPostDetails(params.slug)
39 |
40 | return {
41 | props: {
42 | post: data
43 | }
44 | }
45 | }
46 |
47 | export async function getStaticPaths() {
48 | const posts = await getPosts()
49 | return {
50 | paths: posts.map(({node: {slug}}) => ({params: {slug}})),
51 | fallback: true
52 | }
53 | }
--------------------------------------------------------------------------------
/components/PostWidget.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import moment from 'moment';
3 | import Link from 'next/link';
4 | import { getRecentPosts, getSimilarPosts } from '../services';
5 |
6 | const PostWidget = ({categories, slug}) => {
7 | const [relatedPosts, setRelatedPosts] = useState([])
8 |
9 | useEffect(() => {
10 | if(slug) {
11 | getSimilarPosts(categories, slug)
12 | .then(res => setRelatedPosts(res))
13 | }else{
14 | getRecentPosts()
15 | .then(res => setRelatedPosts(res))
16 | }
17 | }, [slug])
18 |
19 | return (
20 |
21 |
22 | {slug ? 'Related Posts' : 'Recent Posts'}
23 |
24 | {relatedPosts.map((post, index) => (
25 |
26 |
27 |
34 |
35 |
36 |
37 | {moment(post.createdAt).format("MMM DD, YYYY")}
38 |
39 |
40 | {post.title}
41 |
42 |
43 |
44 | ))}
45 |
46 | )
47 | };
48 |
49 | export default PostWidget;
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Demo https://newgraoh.vercel.app/
4 |
5 | *Description*
6 | With popular and latest posts, categories. complete with article markdowns, author info, comments and more, this fully responsive HeadlessCMS Blog app is the best GraphQL blog app. And the best thing is that you or your clients can manage the blog from a dedicated content management system. Built with the latest technologies such as React JS, NextJS, Tailwind CSS, GraphQL and GraphCMS.
7 |
8 | ## Getting Started
9 |
10 | First, run the development server:
11 |
12 | ```bash
13 | npm run dev
14 | # or
15 | yarn dev
16 | ```
17 |
18 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
19 |
20 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
21 |
22 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
23 |
24 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
25 |
26 | ## Learn More
27 |
28 | To learn more about Next.js, take a look at the following resources:
29 |
30 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
31 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
32 |
33 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
34 |
35 | ## Deploy on Vercel
36 |
37 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
38 |
39 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
40 |
--------------------------------------------------------------------------------
/components/PostCard.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import moment from 'moment';
3 | import Link from 'next/link';
4 |
5 | const PostCard = ({post}) => {
6 | return (
7 |
8 |
9 | {post.title}
10 |
11 |
12 |
13 |
20 |
{post.author.name}
21 |
22 |
23 |
24 |
25 |
26 |
27 | {moment(post.createdAt).format('MMM DD, YYYY')}
28 |
29 |
30 |
31 |
32 |
37 |
38 |
{post.excerpt}
39 |
40 |
41 |
42 | Continue Reading
43 |
44 |
45 |
46 |
47 | )
48 | };
49 |
50 | export default PostCard;
51 |
--------------------------------------------------------------------------------
/sections/FeaturedPosts.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import Carousel from "react-multi-carousel";
3 | import "react-multi-carousel/lib/styles.css"
4 | import FeaturedPostCard from "../components/FeaturedPostCard";
5 | import { getFeaturedPosts } from "../services";
6 |
7 | const responsive = {
8 | superLargeDesktop: {
9 | breakpoint: {max: 4000, min: 1024},
10 | items: 5
11 | },
12 | desktop: {
13 | breakpoint: {max: 1024, min: 768},
14 | items: 3
15 | },
16 | tablet: {
17 | breakpoint: {max: 768, min: 640},
18 | items: 2
19 | },
20 | mobile:{
21 | breakpoint: {max: 640, min: 0},
22 | items: 1
23 | }
24 | }
25 |
26 | const FeaturedPosts = () => {
27 | const [featuredPosts, setFeaturedPosts] = useState([])
28 | const [dataLoaded, setDataLoaded] = useState(false)
29 |
30 | useEffect(() => {
31 | getFeaturedPosts()
32 | .then(res => {
33 | setFeaturedPosts(res)
34 | setDataLoaded(true)
35 | })
36 | }, [])
37 |
38 | const customLeftArrow = (
39 |
44 | );
45 |
46 | const customRightArrow = (
47 |
52 | );
53 |
54 | return (
55 |
56 |
66 | {dataLoaded && featuredPosts.map((post, index) => (
67 |
68 | ))}
69 |
70 |
71 | )
72 | }
73 |
74 | export default FeaturedPosts
--------------------------------------------------------------------------------
/components/SinglePost.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from 'react';
2 | import { getSingleSimilarPosts } from '../services';
3 | import moment from 'moment';
4 | import Image from 'next/image';
5 | import Link from 'next/link';
6 |
7 | const SinglePost = ({categories, slug}) => {
8 | const [singlePost, setSinglePost] = useState([])
9 |
10 | useEffect(() => {
11 | getSingleSimilarPosts(categories, slug)
12 | .then((res) => setSinglePost(res))
13 | }, [])
14 | console.log(singlePost)
15 |
16 | return (
17 | <>
18 | {singlePost.map((post, index) => (
19 |
20 |
21 |
22 |
23 |
{moment(post.createdAt).format('MMM DD, YYYY')}
24 |
{post.title}
25 |
26 |
34 |
{post.author.name}
35 |
36 |
37 |
38 |
43 |
44 |
45 | ))}
46 | >
47 | )
48 | };
49 |
50 | export default SinglePost;
51 |
--------------------------------------------------------------------------------
/components/PostDetail.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import moment from 'moment';
3 |
4 | const PostDetail = ({post}) => {
5 | const getContentFragment = (index, text, obj, type) => {
6 | let modifiedText = text
7 |
8 | if(obj) {
9 | if(obj.bold) {
10 | modifiedText = ({text} )
11 | }
12 | if(obj.italic) {
13 | modifiedText = ({text} )
14 | }
15 | if(obj.underline) {
16 | modifiedText = ({text} )
17 | }
18 | }
19 |
20 | switch(type) {
21 | case 'heading-three':
22 | return {modifiedText.map((item, i) => {item} )}
23 | case 'paragraph':
24 | return {modifiedText.map((item, i) => {item} )}
25 | case 'image':
26 | return(
27 |
34 | )
35 | default:
36 | return modifiedText
37 | }
38 | }
39 |
40 | return (
41 |
42 |
43 |
48 |
49 |
50 |
51 |
52 |
59 |
{post.author.name}
60 |
61 |
62 |
63 |
64 |
65 |
66 | {moment(post.createdAt).format('MMM DD, YYYY')}
67 |
68 |
69 |
70 |
{post.title}
71 | {post.content.raw.children.map((typeObj, index) => {
72 | const children = typeObj.children.map((item, itemIndex) => getContentFragment(itemIndex, item.text, item))
73 | return getContentFragment(index, children, typeObj, typeObj.type)
74 | })}
75 |
76 |
77 | )
78 | };
79 |
80 | export default PostDetail;
81 |
--------------------------------------------------------------------------------
/components/CommentsForm.jsx:
--------------------------------------------------------------------------------
1 | import React, {useRef, useState, useEffect} from 'react';
2 |
3 | import { submitComment } from '../services';
4 |
5 | const CommentsForm = ({slug}) => {
6 | const [error, setError] = useState(false)
7 | const [localStorage, setLocalStorage] = useState(null)
8 | const [showSuccessMessage, setShowSuccessMessage] = useState(false)
9 | const commentEl = useRef()
10 | const nameEl = useRef()
11 | const emailEl = useRef()
12 | const storeDataEl = useRef()
13 |
14 | const handleCommentSubmission = () => {
15 | setError(false)
16 |
17 | const {value: comment} = commentEl.current
18 | const {value: name} = nameEl.current
19 | const {value: email} = emailEl.current
20 | const {checked: storeData} = storeDataEl.current
21 |
22 | if(!name || !comment || !email) {
23 | setError(true)
24 | return
25 | }
26 |
27 | const commentsObj = {name, email, comment, slug}
28 |
29 | if(storeData) {
30 | window.localStorage.setItem("name", name)
31 | window.localStorage.setItem("email", email)
32 | }else {
33 | window.localStorage.removeItem("name")
34 | window.localStorage.removeItem("email")
35 | }
36 |
37 | submitComment(commentsObj)
38 | .then(res => {
39 | setShowSuccessMessage(true)
40 | setTimeout(() => {
41 | setShowSuccessMessage(false)
42 | }, 3000)
43 | })
44 | }
45 |
46 | return (
47 |
48 |
Comments
49 |
50 |
56 |
57 |
58 |
65 |
72 |
73 |
74 |
75 |
76 | Save my name, email in this browser for the next time I comment.
77 |
78 |
79 | {error &&
All fields are required
}
80 |
81 | Post Comment
86 | {showSuccessMessage && Comment submitted for review }
87 |
88 |
89 | )
90 | };
91 |
92 | export default CommentsForm;
93 |
--------------------------------------------------------------------------------
/services/index.js:
--------------------------------------------------------------------------------
1 | import { request, gql } from "graphql-request";
2 |
3 | const grapqhlAPI = process.env.NEXT_PUBLIC_GRAPHCMS_ENDPOINT
4 |
5 | export const getPosts = async () => {
6 | const query = gql`
7 | query MyQuery {
8 | postsConnection {
9 | edges {
10 | node {
11 | author {
12 | bio
13 | name
14 | id
15 | photo {
16 | url
17 | }
18 | }
19 | createdAt
20 | slug
21 | title
22 | excerpt
23 | featuredImage {
24 | url
25 | }
26 | categories {
27 | name
28 | slug
29 | }
30 | }
31 | }
32 | }
33 | }
34 | `
35 |
36 | const result = await request(grapqhlAPI, query)
37 | return result.postsConnection.edges
38 | }
39 |
40 | export const getPostDetails = async(slug) => {
41 | const query = gql`
42 | query GetPostDetails($slug: String!){
43 | post(where: {slug: $slug}) {
44 | author {
45 | bio
46 | name
47 | id
48 | photo{
49 | url
50 | }
51 | }
52 | createdAt
53 | slug
54 | title
55 | excerpt
56 | featuredImage{
57 | url
58 | }
59 | categories {
60 | name
61 | slug
62 | }
63 | content {
64 | raw
65 | }
66 | }
67 | }
68 | `
69 |
70 | const result = await request(grapqhlAPI, query, {slug})
71 | return result.post
72 | }
73 |
74 | export const getCategoryPost = async(slug) => {
75 | const query = gql`
76 | query GetCategoryPost($slug: String!){
77 | postsConnection(where: {categories_some: {slug: $slug}}) {
78 | edges {
79 | node {
80 | author {
81 | bio
82 | name
83 | id
84 | photo {
85 | url
86 | }
87 | }
88 | createdAt
89 | slug
90 | title
91 | excerpt
92 | featuredImage {
93 | url
94 | }
95 | categories {
96 | name
97 | slug
98 | }
99 | }
100 | }
101 | }
102 | }
103 | `
104 |
105 | const result = await request(grapqhlAPI, query, {slug})
106 | return result.postsConnection.edges
107 | }
108 |
109 | export const getRecentPosts = async() => {
110 | const query = gql`
111 | query GetPostDetails() {
112 | posts(
113 | orderBy: createdAt_ASC
114 | last: 3
115 | ) {
116 | title
117 | featuredImage{
118 | url
119 | }
120 | createdAt
121 | slug
122 | }
123 | }
124 | `
125 |
126 | const result = await request(grapqhlAPI, query)
127 | return result.posts
128 | }
129 |
130 | export const getSimilarPosts = async(categories, slug) => {
131 | const query = gql`
132 | query GetPostDetails($slug: String!, $categories: [String!]){
133 | posts(
134 | where: {slug_not: $slug, AND: {categories_some: {slug_in: $categories}}}
135 | last: 3
136 | ) {
137 | title
138 | featuredImage{
139 | url
140 | }
141 | createdAt
142 | slug
143 | }
144 | }
145 | `
146 |
147 | const result = await request(grapqhlAPI, query, {categories, slug})
148 | return result.posts
149 | }
150 |
151 | export const getCategories = async() => {
152 | const query = gql`
153 | query getCategories {
154 | categories {
155 | name
156 | slug
157 | }
158 | }
159 | `
160 |
161 | const result = await request(grapqhlAPI, query)
162 | return result.categories
163 | }
164 |
165 | export const submitComment = async (obj) => {
166 | const result = await fetch('/api/comments', {
167 | method: 'POST',
168 | headers: {
169 | 'Content-Type': 'application/json',
170 | },
171 | body: JSON.stringify(obj),
172 | });
173 |
174 | return result.json();
175 | };
176 |
177 |
178 | export const getComments = async(slug) => {
179 | const query = gql `
180 | query GetComments($slug: String!) {
181 | comments(where: {post: {slug: $slug}}) {
182 | name
183 | createdAt
184 | comment
185 | }
186 | }
187 | `
188 |
189 | const result = await request(grapqhlAPI, query, {slug})
190 | return result.comments
191 | }
192 |
193 | export const getFeaturedPosts = async() => {
194 | const query = gql`
195 | query GetCategoryPost() {
196 | posts(where: {featuredPost: true}) {
197 | author {
198 | name
199 | photo {
200 | url
201 | }
202 | }
203 | featuredImage{
204 | url
205 | }
206 | title
207 | slug
208 | createdAt
209 | }
210 | }
211 | `
212 |
213 | const result = await request(grapqhlAPI, query)
214 | return result.posts
215 | }
216 |
217 | export const getSingleSimilarPosts = async(categories, slug) => {
218 |
219 | const query = gql`
220 | query GetPostDetails($slug: String!, $categories: [String!]){
221 | posts(
222 | where: {slug_not: $slug, AND: {categories_some: {slug_in: $categories}}}
223 | first: 1
224 | ) {
225 | title
226 | featuredImage{
227 | url
228 | }
229 | createdAt
230 | slug
231 | author {
232 | name
233 | photo {
234 | url
235 | }
236 | }
237 | }
238 | }
239 | `
240 |
241 | const result = await request(grapqhlAPI, query, {categories, slug})
242 | return result.posts
243 | }
--------------------------------------------------------------------------------