Skip to content

Commit e32e435

Browse files
committed
Add comment form
1 parent c4463a2 commit e32e435

File tree

9 files changed

+353
-5
lines changed

9 files changed

+353
-5
lines changed

next.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ module.exports = {
2020
* @see https://nextjs.org/docs/basic-features/image-optimization#domains
2121
*/
2222
images: {
23-
domains: [ allowedImageWordPressDomain, 'via.placeholder.com' ],
23+
domains: [ allowedImageWordPressDomain, 'via.placeholder.com', 'secure.gravatar.com' ],
2424
},
2525
}

pages/blog/[slug].js

+9-3
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ import Layout from '../../src/components/layout';
1212
import { FALLBACK, handleRedirectsAndReturnData } from '../../src/utils/slug';
1313
import { getFormattedDate, sanitize } from '../../src/utils/miscellaneous';
1414
import { HEADER_FOOTER_ENDPOINT } from '../../src/utils/constants/endpoints';
15-
import { getPost, getPosts } from '../../src/utils/blog';
15+
import { getComments, getPost, getPosts } from '../../src/utils/blog';
1616
import Image from '../../src/components/image';
1717
import PostMeta from '../../src/components/post-meta';
18+
import Comments from '../../src/components/comments';
1819

19-
const Post = ( { headerFooter, postData } ) => {
20+
const Post = ( { headerFooter, postData, commentsData } ) => {
2021
const router = useRouter();
22+
23+
console.log( 'commentsData', commentsData );
2124

2225
/**
2326
* If the page is not yet generated, this will be displayed
@@ -43,6 +46,7 @@ const Post = ( { headerFooter, postData } ) => {
4346
<PostMeta date={ getFormattedDate( postData?.date ?? '' ) } authorName={ postData?._embedded?.author?.[0]?.name ?? '' }/>
4447
<h1 dangerouslySetInnerHTML={ { __html: sanitize( postData?.title?.rendered ?? '' ) } }/>
4548
<div dangerouslySetInnerHTML={ { __html: sanitize( postData?.content?.rendered ?? '' ) } }/>
49+
<Comments comments={ commentsData }/>
4650
</div>
4751
</Layout>
4852
);
@@ -53,11 +57,13 @@ export default Post;
5357
export async function getStaticProps( { params } ) {
5458
const { data: headerFooterData } = await axios.get( HEADER_FOOTER_ENDPOINT );
5559
const postData = await getPost( params?.slug ?? '' );
60+
const commentsData = await getComments( postData?.[0]?.id ?? 0 );
5661

5762
const defaultProps = {
5863
props: {
5964
headerFooter: headerFooterData?.data ?? {},
60-
postData: postData?.[0] ?? {}
65+
postData: postData?.[0] ?? {},
66+
commentsData: commentsData || []
6167
},
6268
/**
6369
* Revalidate means that if a new request comes to server, then every 1 sec it will check
+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { useState } from 'react';
2+
import Error from './error';
3+
import validateAndSanitizeCommentsForm from '../../validator/comments';
4+
5+
const CommentForm = () => {
6+
7+
const [ input, setInput ] = useState( {} );
8+
9+
const handleFormSubmit = ( event ) => {
10+
event.preventDefault();
11+
12+
const commentFormValidationResult = validateAndSanitizeCommentsForm( input );
13+
console.log( 'commentFormValidationResult', commentFormValidationResult, input );
14+
15+
setInput( {
16+
...input,
17+
errors: commentFormValidationResult.errors,
18+
} );
19+
20+
// If there are any errors, return.
21+
if ( ! commentFormValidationResult.isValid ) {
22+
return null;
23+
}
24+
}
25+
26+
/*
27+
* Handle onchange input.
28+
*
29+
* @param {Object} event Event Object.
30+
* @param {bool} isShipping If this is false it means it is billing.
31+
* @param {bool} isBillingOrShipping If this is false means its standard input and not billing or shipping.
32+
*
33+
* @return {void}
34+
*/
35+
const handleOnChange = ( event ) => {
36+
const { target } = event || {};
37+
38+
const newState = { ...input, [ target.name ]: target.value };
39+
setInput( newState );
40+
};
41+
42+
return (
43+
<form action="/" noValidate onSubmit={ handleFormSubmit }>
44+
<h2>Leave a comment</h2>
45+
<p className="comment-notes">
46+
<span id="email-notes">Your email address will not be published.</span>
47+
<span className="required-field-message">Required fields are marked <span className="required">*</span></span>
48+
</p>
49+
<div className="comment-form-comment mb-2">
50+
<label htmlFor="comment" className="block mb-2 font-medium text-gray-900 dark:text-white">
51+
Comment
52+
<span className="required">*</span>
53+
</label>
54+
<textarea
55+
id="comment"
56+
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
57+
name="comment"
58+
cols="45"
59+
rows="5"
60+
maxLength="65525"
61+
required
62+
value={ input?.comment ?? '' }
63+
onChange={ ( event ) => handleOnChange( event ) }
64+
/>
65+
<Error errors={ input?.errors ?? {} } fieldName="comment"/>
66+
</div>
67+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-2">
68+
<div className="comment-form-author">
69+
<label htmlFor="author" className="block mb-2 font-medium text-gray-900 dark:text-white">
70+
Name
71+
<span className="required">*</span>
72+
</label>
73+
<input
74+
id="author"
75+
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 w-full block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
76+
name="author"
77+
type="text"
78+
size="30"
79+
maxLength="245"
80+
autoComplete="name"
81+
required=""
82+
value={ input?.author ?? '' }
83+
onChange={ ( event ) => handleOnChange( event ) }
84+
/>
85+
<Error errors={ input?.errors ?? {} } fieldName="author"/>
86+
</div>
87+
<div className="comment-form-email border-0 mb-2">
88+
<label htmlFor="email" className="block mb-2 font-medium text-gray-900 dark:text-white">
89+
Email
90+
<span className="required">*</span>
91+
</label>
92+
<input
93+
id="email"
94+
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 w-full block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
95+
name="email"
96+
type="email"
97+
size="30"
98+
maxLength="100"
99+
aria-describedby="email-notes"
100+
autoComplete="email"
101+
required
102+
value={ input?.email ?? '' }
103+
onChange={ ( event ) => handleOnChange( event ) }
104+
/>
105+
<Error errors={ input?.errors ?? {} } fieldName="email"/>
106+
</div>
107+
</div>
108+
<div className="comment-form-url mb-2">
109+
<label htmlFor="url" className="block mb-2 font-medium text-gray-900 dark:text-white">
110+
Website
111+
</label>
112+
<input
113+
id="url"
114+
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
115+
name="url"
116+
type="url"
117+
size="30"
118+
maxLength="200"
119+
autoComplete="url"
120+
value={ input?.url ?? '' }
121+
onChange={ ( event ) => handleOnChange( event ) }
122+
/>
123+
<Error errors={ input?.errors ?? {} } fieldName="url"/>
124+
</div>
125+
<div className="comment-form-cookies-consent mb-4">
126+
<input
127+
id="wp-comment-cookies-consent"
128+
name="wp_comment_cookies_consent"
129+
type="checkbox"
130+
value="yes"
131+
onChange={ ( event ) => handleOnChange( event ) }
132+
/>
133+
<label htmlFor="wp-comment-cookies-consent" className="mb-2 ml-2 font-medium text-gray-900 dark:text-white">
134+
Save my name, email, and website in this browser for the next time I comment.
135+
</label>
136+
<Error errors={ input?.errors ?? {} } fieldName="wp_comment_cookies_consent"/>
137+
</div>
138+
<div className="form-submit">
139+
<input name="submit" type="submit" id="submit" className=" cursor-pointer text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-16px uppercase w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800" value="Post Comment"/>
140+
<input type="hidden" name="comment_post_ID" value="377" id="comment_post_ID"/>
141+
<input type="hidden" name="comment_parent" id="comment_parent" value="0"/>
142+
</div>
143+
</form>
144+
)
145+
}
146+
147+
export default CommentForm;

src/components/comments/comment.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { isEmpty } from 'lodash';
2+
import { getFormattedDate, sanitize } from '../../utils/miscellaneous';
3+
import Image from '../image';
4+
5+
const Comment = ( { comment } ) => {
6+
7+
if ( isEmpty( comment ) ) {
8+
return null;
9+
}
10+
11+
return (
12+
<article className="p-6 mb-6 text-base bg-white border-t border-gray-200 dark:border-gray-700 dark:bg-gray-900">
13+
<footer className="flex justify-between items-center mb-4">
14+
<div className="flex items-center">
15+
<div className="inline-flex items-center mr-3 text-sm text-gray-900 dark:text-white">
16+
<Image
17+
sourceUrl={ comment?.author_avatar_urls?.['48'] ?? '' }
18+
title={ comment?.author_name ?? '' }
19+
width="24"
20+
height="24"
21+
layout="fill"
22+
containerClassNames="mr-2 w-9 h-9"
23+
style={{borderRadius: '50%', overflow: 'hidden'}}
24+
/>
25+
{ comment?.author_name ?? '' }
26+
</div>
27+
<div className="text-sm text-gray-600 dark:text-gray-400">
28+
<time dateTime={ comment?.date ?? '' } title="March 12th, 2022">{ getFormattedDate( comment?.date ?? '' ) }</time>
29+
</div>
30+
</div>
31+
</footer>
32+
<div
33+
className="text-gray-500 dark:text-gray-400"
34+
dangerouslySetInnerHTML={ { __html: sanitize( comment?.content?.rendered ?? '' ) } }
35+
/>
36+
<div className="flex items-center mt-4 space-x-4">
37+
<button type="button"
38+
className="flex items-center text-sm text-gray-500 hover:underline dark:text-gray-400">
39+
<svg aria-hidden="true" className="mr-1 w-4 h-4" fill="none" stroke="currentColor"
40+
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
41+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"
42+
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
43+
</svg>
44+
Reply
45+
</button>
46+
</div>
47+
</article>
48+
)
49+
}
50+
51+
export default Comment;

src/components/comments/error.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const Error = ( { errors, fieldName } ) => {
2+
3+
return(
4+
errors && ( errors.hasOwnProperty( fieldName ) ) ? (
5+
<div className="invalid-feedback d-block text-red-500">{ errors[fieldName] }</div>
6+
) : ''
7+
)
8+
};
9+
10+
export default Error;

src/components/comments/index.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { isArray, isEmpty } from 'lodash';
2+
import Comment from './comment';
3+
import CommentForm from './comment-form';
4+
5+
const Comments = ( { comments } ) => {
6+
7+
if ( isEmpty( comments ) || ! isArray( comments ) ) {
8+
return null;
9+
}
10+
return (
11+
<div className="mt-20">
12+
<h2>{ comments.length } Comments</h2>
13+
{
14+
comments.map( ( comment, index ) => {
15+
return (
16+
<div
17+
key={ `${ comment?.id ?? '' }-${ index }` ?? '' }
18+
className="comment"
19+
>
20+
<Comment comment={ comment }/>
21+
</div>
22+
);
23+
} )
24+
}
25+
<CommentForm />
26+
</div>
27+
)
28+
}
29+
30+
export default Comments;

src/utils/blog.js

+26-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import axios from 'axios';
66
/**
77
* Internal Dependencies.
88
*/
9-
import { GET_PAGES_ENDPOINT, GET_POST_ENDPOINT, GET_POSTS_ENDPOINT } from './constants/endpoints';
9+
import {
10+
GET_COMMENTS_ENDPOINT,
11+
GET_PAGES_ENDPOINT,
12+
GET_POST_ENDPOINT,
13+
GET_POSTS_ENDPOINT,
14+
} from './constants/endpoints';
1015

1116
/**
1217
* Get Posts.
@@ -93,3 +98,23 @@ export const getPage = async ( pageSlug = '' ) => {
9398
return [];
9499
} );
95100
};
101+
102+
/**
103+
* Get Post By Slug.
104+
*
105+
* @return {Promise<void>}
106+
*/
107+
export const getComments = async ( postID = '' ) => {
108+
return await axios.get( `${ GET_COMMENTS_ENDPOINT }?slug=${ postID }&_embed` )
109+
.then( res => {
110+
if ( 200 === res.status ) {
111+
return res.data;
112+
} else {
113+
return [];
114+
}
115+
} )
116+
.catch( err => {
117+
console.log( err.response.data.message )
118+
return [];
119+
} );
120+
};

src/utils/constants/endpoints.js

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export const HEADER_FOOTER_ENDPOINT = `${ process.env.NEXT_PUBLIC_WORDPRESS_SITE
22
export const GET_POSTS_ENDPOINT = `${ process.env.NEXT_PUBLIC_WORDPRESS_SITE_URL }/wp-json/rae/v1/posts`;
33
export const GET_POST_ENDPOINT = `${ process.env.NEXT_PUBLIC_WORDPRESS_SITE_URL }/wp-json/wp/v2/posts`;
44
export const GET_PAGES_ENDPOINT = `${ process.env.NEXT_PUBLIC_WORDPRESS_SITE_URL }/wp-json/wp/v2/pages`;
5+
export const GET_COMMENTS_ENDPOINT = `${ process.env.NEXT_PUBLIC_WORDPRESS_SITE_URL }/wp-json/wp/v2/comments`;
56

67
/**
78
* Cart

0 commit comments

Comments
 (0)