├── 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 | {author.name} 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 | 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 |
17 |
18 |
19 | 20 | 21 |
22 |
23 |
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 |
18 |
19 | 20 |
21 |
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 | {post.author.name} 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 | {post.title} 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 | {post.author.name} 20 |

{post.author.name}

21 |
22 |
23 | 24 | 25 | 26 | 27 | {moment(post.createdAt).format('MMM DD, YYYY')} 28 | 29 |
30 |
31 |
32 | {post.title} 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 |
40 | 41 | 42 | 43 |
44 | ); 45 | 46 | const customRightArrow = ( 47 |
48 | 49 | 50 | 51 |
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 | {post.author.name} 34 |

{post.author.name}

35 |
36 |
37 | 38 |
39 | 40 | 41 | 42 |
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 | {obj.title} 34 | ) 35 | default: 36 | return modifiedText 37 | } 38 | } 39 | 40 | return ( 41 |
42 |
43 | {post.title} 48 |
49 |
50 |
51 |
52 | {post.author.name} 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 |