summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2021-12-17 17:58:03 +0100
committerArmand Philippot <git@armandphilippot.com>2021-12-17 17:58:03 +0100
commitabf6e96383035f99addab804e8c3dd1a74d36375 (patch)
tree066d672466495f948885379ac8fd7fb1228ad4cf
parent8a703cb39ff23ff3639b0da33f0d72f92f1cc55b (diff)
chore: display comments list on posts
-rw-r--r--src/components/Comment/Comment.module.scss87
-rw-r--r--src/components/Comment/Comment.tsx88
-rw-r--r--src/components/CommentsList/CommentsList.module.scss5
-rw-r--r--src/components/CommentsList/CommentsList.tsx24
-rw-r--r--src/pages/article/[slug].tsx6
-rw-r--r--src/services/graphql/post.ts5
-rw-r--r--src/ts/types/comments.ts12
7 files changed, 225 insertions, 2 deletions
diff --git a/src/components/Comment/Comment.module.scss b/src/components/Comment/Comment.module.scss
new file mode 100644
index 0000000..8f17bfa
--- /dev/null
+++ b/src/components/Comment/Comment.module.scss
@@ -0,0 +1,87 @@
+@use "@styles/abstracts/functions" as fun;
+@use "@styles/abstracts/mixins" as mix;
+
+.item {
+ margin: var(--spacing-sm) 0;
+}
+
+.wrapper {
+ border: fun.convert-px(1) solid var(--color-border-light);
+ box-shadow: fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-shadow-light),
+ fun.convert-px(4) fun.convert-px(4) fun.convert-px(3) fun.convert-px(-2)
+ var(--color-shadow-light);
+ padding: var(--spacing-md);
+ position: relative;
+
+ @include mix.media("screen") {
+ @include mix.dimensions("sm") {
+ display: grid;
+ grid-template-columns: minmax(#{fun.convert-px(150)}, 1fr) minmax(0, 85ch);
+ column-gap: var(--spacing-lg);
+ }
+ }
+}
+
+.header {
+ display: flex;
+ flex-flow: column wrap;
+ align-items: center;
+ row-gap: var(--spacing-sm);
+
+ @include mix.media("screen") {
+ @include mix.dimensions("sm") {
+ grid-row: 1 / 4;
+ }
+ }
+}
+
+.avatar {
+ width: fun.convert-px(85);
+ height: fun.convert-px(85);
+ border-radius: fun.convert-px(3);
+ box-shadow: 0 0 0 fun.convert-px(1) var(--color-shadow-light),
+ fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-shadow-light);
+ position: relative;
+}
+
+.date {
+ color: var(--color-fg-secondary);
+ font-size: var(--font-size-sm);
+ display: grid;
+ grid-template-columns: repeat(2, max-content);
+ column-gap: var(--spacing-2xs);
+ justify-content: center;
+ margin: var(--spacing-sm) 0 var(--spacing-md);
+
+ @include mix.media("screen") {
+ @include mix.dimensions("sm") {
+ justify-content: left;
+ margin: 0 0 var(--spacing-md);
+ }
+ }
+}
+
+.body {
+ overflow-wrap: break-word;
+}
+
+.footer {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ padding: var(--spacing-md) 0 0;
+
+ @include mix.media("screen") {
+ @include mix.dimensions("sm") {
+ padding: var(--spacing-sm) 0 0;
+ }
+ }
+
+ button {
+ margin: 0;
+ }
+}
+
+.list {
+ padding-left: var(--spacing-md);
+}
diff --git a/src/components/Comment/Comment.tsx b/src/components/Comment/Comment.tsx
new file mode 100644
index 0000000..ad6875c
--- /dev/null
+++ b/src/components/Comment/Comment.tsx
@@ -0,0 +1,88 @@
+import { Button } from '@components/Buttons';
+import { t } from '@lingui/macro';
+import { Comment as CommentData } from '@ts/types/comments';
+import Image from 'next/image';
+import Link from 'next/link';
+import { useRouter } from 'next/router';
+import styles from './Comment.module.scss';
+
+const Comment = ({ comment }: { comment: CommentData }) => {
+ const router = useRouter();
+
+ const getCommentAuthor = () => {
+ return comment.author.url ? (
+ <Link href={comment.author.url}>
+ <a>{comment.author.name}</a>
+ </Link>
+ ) : (
+ <span>{comment.author.name}</span>
+ );
+ };
+
+ const getLocaleDate = () => {
+ const commentDate = new Date(comment.date);
+ const date = commentDate.toLocaleDateString(router.locale, {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ });
+ const time = commentDate
+ .toLocaleTimeString(router.locale, {
+ hour: 'numeric',
+ minute: 'numeric',
+ })
+ .replace(':', 'h');
+ return t`${date} at ${time}`;
+ };
+
+ const getApprovedComment = () => {
+ return (
+ <>
+ <article className={styles.wrapper}>
+ <header className={styles.header}>
+ {comment.author.gravatarUrl && (
+ <div className={styles.avatar}>
+ <Image
+ src={comment.author.gravatarUrl}
+ alt={comment.author.name}
+ layout="fill"
+ />
+ </div>
+ )}
+ {getCommentAuthor()}
+ </header>
+ <dl className={styles.date}>
+ <dt>{t`Published on:`}</dt>
+ <dd>{getLocaleDate()}</dd>
+ </dl>
+ <div
+ className={styles.body}
+ dangerouslySetInnerHTML={{ __html: comment.content }}
+ ></div>
+ <footer className={styles.footer}>
+ <Button clickHandler={() => ''}>{t`Reply`}</Button>
+ </footer>
+ </article>
+ {comment.replies.length > 0 && (
+ <ol className={styles.list}>
+ {comment.replies.map((reply) => {
+ return <Comment key={reply.id} comment={reply} />;
+ })}
+ </ol>
+ )}
+ </>
+ );
+ };
+
+ const getCommentStatus = () => {
+ return <p>{t`This comment is awaiting moderation.`}</p>;
+ };
+
+ return (
+ <li className={styles.item}>
+ {comment.approved ? getApprovedComment() : getCommentStatus()}
+ </li>
+ );
+};
+
+export default Comment;
diff --git a/src/components/CommentsList/CommentsList.module.scss b/src/components/CommentsList/CommentsList.module.scss
new file mode 100644
index 0000000..2358948
--- /dev/null
+++ b/src/components/CommentsList/CommentsList.module.scss
@@ -0,0 +1,5 @@
+@use "@styles/abstracts/placeholders";
+
+.list {
+ @extend %reset-ordered-list;
+}
diff --git a/src/components/CommentsList/CommentsList.tsx b/src/components/CommentsList/CommentsList.tsx
new file mode 100644
index 0000000..6599475
--- /dev/null
+++ b/src/components/CommentsList/CommentsList.tsx
@@ -0,0 +1,24 @@
+import { Comment as CommentData } from '@ts/types/comments';
+import Comment from '@components/Comment/Comment';
+import { t } from '@lingui/macro';
+import styles from './CommentsList.module.scss';
+
+const CommentsList = ({ comments }: { comments: CommentData[] }) => {
+ const getCommentsList = () => {
+ return comments.map((comment) => {
+ return <Comment key={comment.id} comment={comment} />;
+ });
+ };
+
+ return (
+ <>
+ {comments.length > 0 ? (
+ <ol className={styles.list}>{getCommentsList()}</ol>
+ ) : (
+ <p>{t`No comments yet.`}</p>
+ )}
+ </>
+ );
+};
+
+export default CommentsList;
diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx
index 7ad3692..b3ab2ed 100644
--- a/src/pages/article/[slug].tsx
+++ b/src/pages/article/[slug].tsx
@@ -1,4 +1,6 @@
+import CommentsList from '@components/CommentsList/CommentsList';
import Layout from '@components/Layouts/Layout';
+import { t } from '@lingui/macro';
import { fetchAllPostsSlug } from '@services/graphql/blog';
import { getPostBySlug } from '@services/graphql/post';
import { NextPageWithLayout } from '@ts/types/app';
@@ -16,6 +18,10 @@ const SingleArticle: NextPageWithLayout<ArticleProps> = ({ post }) => {
<div dangerouslySetInnerHTML={{ __html: post.intro }}></div>
</header>
<div dangerouslySetInnerHTML={{ __html: post.content }}></div>
+ <section>
+ <h2>{t`Comments`}</h2>
+ <CommentsList comments={post.comments} />
+ </section>
</article>
);
};
diff --git a/src/services/graphql/post.ts b/src/services/graphql/post.ts
index 2ce918b..e827fd4 100644
--- a/src/services/graphql/post.ts
+++ b/src/services/graphql/post.ts
@@ -104,7 +104,10 @@ const fetchPostBySlug: FetchPostByReturn = async (slug: string) => {
export const getPostBySlug: GetPostByReturn = async (slug: string) => {
const rawPost = await fetchPostBySlug(slug);
- const comments = rawPost.postBy.comments.nodes;
+ const comments = rawPost.postBy.comments.nodes.reverse().map((comment) => {
+ const author = comment.author.node;
+ return { ...comment, author: author, replies: [] };
+ });
const content = rawPost.postBy.contentParts.afterMore;
const featuredImage = rawPost.postBy.featuredImage
? rawPost.postBy.featuredImage.node
diff --git a/src/ts/types/comments.ts b/src/ts/types/comments.ts
index b196142..51852d0 100644
--- a/src/ts/types/comments.ts
+++ b/src/ts/types/comments.ts
@@ -4,6 +4,10 @@ export type CommentAuthor = {
url: string;
};
+export type CommentAuthorResponse = {
+ node: CommentAuthor;
+};
+
export type Comment = {
approved: '';
author: CommentAuthor;
@@ -11,8 +15,14 @@ export type Comment = {
content: string;
date: string;
id: string;
+ parentDatabaseId: number;
+ replies: Comment[];
+};
+
+export type RawComment = Omit<Comment, 'author'> & {
+ author: CommentAuthorResponse;
};
export type CommentsResponse = {
- nodes: Comment[];
+ nodes: RawComment[];
};