aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/organisms/layout/comment.tsx
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-05-24 19:35:12 +0200
committerGitHub <noreply@github.com>2022-05-24 19:35:12 +0200
commitc85ab5ad43ccf52881ee224672c41ec30021cf48 (patch)
tree8058808d9bfca19383f120c46b34d99ff2f89f63 /src/components/organisms/layout/comment.tsx
parent52404177c07a2aab7fc894362fb3060dff2431a0 (diff)
parent11b9de44a4b2f305a6a484187805e429b2767118 (diff)
refactor: use storybook and atomic design (#16)
BREAKING CHANGE: rewrite most of the Typescript types, so the content format (the meta in particular) needs to be updated.
Diffstat (limited to 'src/components/organisms/layout/comment.tsx')
-rw-r--r--src/components/organisms/layout/comment.tsx171
1 files changed, 171 insertions, 0 deletions
diff --git a/src/components/organisms/layout/comment.tsx b/src/components/organisms/layout/comment.tsx
new file mode 100644
index 0000000..f62f95c
--- /dev/null
+++ b/src/components/organisms/layout/comment.tsx
@@ -0,0 +1,171 @@
+import Button from '@components/atoms/buttons/button';
+import Link from '@components/atoms/links/link';
+import Meta from '@components/molecules/layout/meta';
+import { type Comment as CommentType } from '@ts/types/app';
+import useSettings from '@utils/hooks/use-settings';
+import Image from 'next/image';
+import Script from 'next/script';
+import { FC, useState } from 'react';
+import { useIntl } from 'react-intl';
+import { type Comment as CommentSchema, type WithContext } from 'schema-dts';
+import CommentForm, { type CommentFormProps } from '../forms/comment-form';
+import styles from './comment.module.scss';
+
+export type CommentProps = Pick<
+ CommentType,
+ 'approved' | 'content' | 'id' | 'meta' | 'parentId'
+> &
+ Pick<CommentFormProps, 'Notice' | 'saveComment'> & {
+ /**
+ * Enable or disable the reply button. Default: true.
+ */
+ canReply?: boolean;
+ };
+
+/**
+ * Comment component
+ *
+ * Render a single comment.
+ */
+const Comment: FC<CommentProps> = ({
+ approved,
+ canReply = true,
+ content,
+ id,
+ meta,
+ Notice,
+ parentId,
+ saveComment,
+ ...props
+}) => {
+ const intl = useIntl();
+ const { website } = useSettings();
+ const [isReplying, setIsReplying] = useState<boolean>(false);
+
+ if (!approved) {
+ return (
+ <div className={styles.wrapper}>
+ {intl.formatMessage({
+ defaultMessage: 'This comment is awaiting moderation...',
+ description: 'Comment: awaiting moderation',
+ id: '6a1Uo6',
+ })}
+ </div>
+ );
+ }
+
+ const { author, date } = meta;
+ const [publicationDate, publicationTime] = date.split(' ');
+
+ const buttonLabel = isReplying
+ ? intl.formatMessage({
+ defaultMessage: 'Cancel reply',
+ description: 'Comment: cancel reply button',
+ id: 'LCorTC',
+ })
+ : intl.formatMessage({
+ defaultMessage: 'Reply',
+ description: 'Comment: reply button',
+ id: 'hzHuCc',
+ });
+ const formTitle = intl.formatMessage({
+ defaultMessage: 'Leave a reply',
+ description: 'Comment: comment form title',
+ id: '2fD5CI',
+ });
+
+ const commentSchema: WithContext<CommentSchema> = {
+ '@context': 'https://schema.org',
+ '@id': `${website.url}/#comment-${id}`,
+ '@type': 'Comment',
+ parentItem: parentId
+ ? { '@id': `${website.url}/#comment-${parentId}` }
+ : undefined,
+ about: { '@type': 'Article', '@id': `${website.url}/#article` },
+ author: {
+ '@type': 'Person',
+ name: author.name,
+ image: author.avatar?.src,
+ url: author.website,
+ },
+ creator: {
+ '@type': 'Person',
+ name: author.name,
+ image: author.avatar?.src,
+ url: author.website,
+ },
+ dateCreated: date,
+ datePublished: date,
+ text: content,
+ };
+
+ return (
+ <>
+ <Script
+ id="schema-comments"
+ type="application/ld+json"
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(commentSchema) }}
+ />
+ <article
+ id={`comment-${id}`}
+ className={`${styles.wrapper} ${styles['wrapper--comment']}`}
+ >
+ <header className={styles.header}>
+ {author.avatar && (
+ <div className={styles.avatar}>
+ <Image
+ src={author.avatar.src}
+ alt={author.avatar.alt}
+ layout="fill"
+ objectFit="cover"
+ {...props}
+ />
+ </div>
+ )}
+ {author.website ? (
+ <Link href={author.website} className={styles.author}>
+ {author.name}
+ </Link>
+ ) : (
+ <span className={styles.author}>{author.name}</span>
+ )}
+ </header>
+ <Meta
+ data={{
+ publication: {
+ date: publicationDate,
+ time: publicationTime,
+ target: `#comment-${id}`,
+ },
+ }}
+ layout="inline"
+ itemsLayout="inline"
+ className={styles.date}
+ groupClassName={styles.date__item}
+ />
+ <div
+ className={styles.body}
+ dangerouslySetInnerHTML={{ __html: content }}
+ />
+ <footer className={styles.footer}>
+ {canReply && (
+ <Button kind="tertiary" onClick={() => setIsReplying(!isReplying)}>
+ {buttonLabel}
+ </Button>
+ )}
+ </footer>
+ </article>
+ {isReplying && (
+ <CommentForm
+ Notice={Notice}
+ parentId={id}
+ saveComment={saveComment}
+ title={formTitle}
+ className={`${styles.wrapper} ${styles['wrapper--form']}`}
+ />
+ )}
+ </>
+ );
+};
+
+export default Comment;