summaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-02-14 15:48:34 +0100
committerArmand Philippot <git@armandphilippot.com>2022-02-14 15:54:54 +0100
commit1b34f85f0e3188861c6804666f02b4495cab033c (patch)
tree0f26c9b73b8cc47c7136bad6167a0f18a08fd835 /src/components
parent637350e4d152de1346857d645bda8443900ec6f4 (diff)
chore: improve comment form user experience
Diffstat (limited to 'src/components')
-rw-r--r--src/components/Comment/Comment.tsx20
-rw-r--r--src/components/CommentForm/CommentForm.tsx150
-rw-r--r--src/components/Notice/Notice.tsx3
-rw-r--r--src/components/Spinner/Spinner.tsx11
4 files changed, 117 insertions, 67 deletions
diff --git a/src/components/Comment/Comment.tsx b/src/components/Comment/Comment.tsx
index ab1dffc..a263771 100644
--- a/src/components/Comment/Comment.tsx
+++ b/src/components/Comment/Comment.tsx
@@ -24,7 +24,7 @@ const Comment = ({
const intl = useIntl();
const router = useRouter();
const locale = router.locale ? router.locale : settings.locales.defaultLocale;
- const [isReply, setIsReply] = useState<boolean>(false);
+ const [shouldOpenForm, setShouldOpenForm] = useState<boolean>(false);
const firstFieldRef = useRef<HTMLInputElement>(null);
useEffect(() => {
@@ -98,21 +98,25 @@ const Comment = ({
></div>
{!isNested && (
<footer className={styles.footer}>
- <Button clickHandler={() => setIsReply((prev) => !prev)}>
- {intl.formatMessage({
- defaultMessage: 'Reply',
- description: 'Comment: reply button',
- })}
+ <Button clickHandler={() => setShouldOpenForm((prev) => !prev)}>
+ {shouldOpenForm
+ ? intl.formatMessage({
+ defaultMessage: 'Cancel reply',
+ description: 'Comment: reply button',
+ })
+ : intl.formatMessage({
+ defaultMessage: 'Reply',
+ description: 'Comment: reply button',
+ })}
</Button>
</footer>
)}
</article>
- {isReply && (
+ {shouldOpenForm && (
<CommentForm
ref={firstFieldRef}
articleId={articleId}
parentId={comment.commentId}
- isReply={isReply}
/>
)}
{comment.replies.length > 0 && (
diff --git a/src/components/CommentForm/CommentForm.tsx b/src/components/CommentForm/CommentForm.tsx
index 0ea3276..762bb75 100644
--- a/src/components/CommentForm/CommentForm.tsx
+++ b/src/components/CommentForm/CommentForm.tsx
@@ -1,68 +1,107 @@
import { ButtonSubmit } from '@components/Buttons';
import { Form, FormItem, Input, TextArea } from '@components/Form';
import Notice from '@components/Notice/Notice';
+import Spinner from '@components/Spinner/Spinner';
import { createComment } from '@services/graphql/mutations';
+import { NoticeType } from '@ts/types/app';
import { ForwardedRef, forwardRef, useState } from 'react';
import { useIntl } from 'react-intl';
import styles from './CommentForm.module.scss';
const CommentForm = (
- {
- articleId,
- parentId = 0,
- isReply = false,
- }: {
- articleId: number;
- parentId?: number;
- isReply?: boolean;
- },
+ { articleId, parentId = 0 }: { articleId: number; parentId?: number },
ref: ForwardedRef<HTMLInputElement>
) => {
const intl = useIntl();
- const [name, setName] = useState('');
- const [email, setEmail] = useState('');
- const [website, setWebsite] = useState('');
- const [message, setMessage] = useState('');
- const [isSuccess, setIsSuccess] = useState(false);
- const [isApproved, setIsApproved] = useState(false);
+ const [name, setName] = useState<string>('');
+ const [email, setEmail] = useState<string>('');
+ const [website, setWebsite] = useState<string>('');
+ const [comment, setComment] = useState<string>('');
+ const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
+ const [notice, setNotice] = useState<string>();
+ const [noticeType, setNoticeType] = useState<NoticeType>('success');
const resetForm = () => {
setName('');
setEmail('');
setWebsite('');
- setMessage('');
+ setComment('');
+ setIsSubmitting(false);
};
- const submitHandler = async (e: SubmitEvent) => {
- e.preventDefault();
+ const isEmptyString = (value: string): boolean => value.trim() === '';
+ const areRequiredFieldsSet = (): boolean =>
+ !isEmptyString(name) && !isEmptyString(email) && !isEmptyString(comment);
- if (name && email && message && articleId) {
- const data = {
- author: name,
- authorEmail: email,
- authorUrl: website,
- content: message,
- parent: parentId,
- commentOn: articleId,
- mutationId: 'createComment',
- };
- const createdComment = await createComment(data);
+ const sendComment = async () => {
+ const data = {
+ author: name,
+ authorEmail: email,
+ authorUrl: website,
+ content: comment,
+ parent: parentId,
+ commentOn: articleId,
+ mutationId: 'createComment',
+ };
- if (createdComment.success) setIsSuccess(true);
- if (isSuccess) {
- resetForm();
- if (createdComment.comment?.approved) setIsApproved(true);
+ const createdComment = await createComment(data);
- setTimeout(() => {
- setIsSuccess(false);
- setIsApproved(false);
- }, 8000);
+ if (createdComment.success) {
+ resetForm();
+ setNoticeType('success');
+ if (createdComment.comment?.approved) {
+ setNotice(
+ intl.formatMessage({
+ defaultMessage: 'Thanks for your comment!',
+ description: 'CommentForm: success notice',
+ })
+ );
+ } else {
+ setNotice(
+ intl.formatMessage({
+ defaultMessage:
+ 'Thanks for your comment! It is now awaiting moderation.',
+ description: 'CommentForm: success notice but awaiting moderation',
+ })
+ );
}
+
+ setTimeout(() => {
+ setNotice(undefined);
+ }, 10000);
+ } else {
+ setNoticeType('error');
+ setNotice(
+ intl.formatMessage({
+ defaultMessage:
+ 'An unexpected error happened. Comment cannot be submitted.',
+ description: 'CommentForm: error notice',
+ })
+ );
+ }
+ };
+
+ const submitHandler = async (e: SubmitEvent) => {
+ e.preventDefault();
+ setNotice(undefined);
+ setIsSubmitting(true);
+
+ if (areRequiredFieldsSet()) {
+ sendComment();
} else {
- setIsSuccess(false);
+ setIsSubmitting(false);
+ setNoticeType('warning');
+ setNotice(
+ intl.formatMessage({
+ defaultMessage:
+ 'Some required fields are empty. Comment cannot be submitted.',
+ description: 'CommentForm: missing required fields',
+ })
+ );
}
};
+ const isReply = parentId !== 0;
const wrapperClasses = `${styles.wrapper} ${
isReply ? styles['wrapper--reply'] : ''
}`;
@@ -72,7 +111,7 @@ const CommentForm = (
<h2 className={styles.title}>
{intl.formatMessage({
defaultMessage: 'Leave a comment',
- description: 'CommentForm: form title',
+ description: 'CommentForm: Form title',
})}
</h2>
<Form
@@ -97,6 +136,7 @@ const CommentForm = (
<Input
id="commenter-email"
name="commenter-email"
+ type="email"
label={intl.formatMessage({
defaultMessage: 'Email',
description: 'CommentForm: Email field label',
@@ -120,18 +160,24 @@ const CommentForm = (
</FormItem>
<FormItem>
<TextArea
- id="commenter-message"
- name="commenter-message"
+ id="commenter-comment"
+ name="commenter-comment"
label={intl.formatMessage({
defaultMessage: 'Comment',
description: 'CommentForm: Comment field label',
})}
- value={message}
- setValue={setMessage}
+ value={comment}
+ setValue={setComment}
required={true}
/>
</FormItem>
<FormItem>
+ <noscript>
+ {intl.formatMessage({
+ defaultMessage: 'Javascript is required to post a comment.',
+ description: 'CommentForm: noscript tag',
+ })}
+ </noscript>
<ButtonSubmit>
{intl.formatMessage({
defaultMessage: 'Send',
@@ -139,16 +185,16 @@ const CommentForm = (
})}
</ButtonSubmit>
</FormItem>
- {isSuccess && !isApproved && (
- <Notice type="success">
- {intl.formatMessage({
- defaultMessage:
- 'Thanks for your comment! It is now awaiting moderation.',
- description: 'CommentForm: Comment sent success message',
- })}
- </Notice>
- )}
</Form>
+ {isSubmitting && (
+ <Spinner
+ message={intl.formatMessage({
+ defaultMessage: 'Submitting...',
+ description: 'CommentForm: submitting message',
+ })}
+ />
+ )}
+ {notice && <Notice type={noticeType}>{notice}</Notice>}
</div>
);
};
diff --git a/src/components/Notice/Notice.tsx b/src/components/Notice/Notice.tsx
index 472efa5..02b1f12 100644
--- a/src/components/Notice/Notice.tsx
+++ b/src/components/Notice/Notice.tsx
@@ -1,8 +1,7 @@
+import { NoticeType } from '@ts/types/app';
import { ReactNode } from 'react';
import styles from './Notice.module.scss';
-type NoticeType = 'error' | 'info' | 'success' | 'warning';
-
const Notice = ({
children,
type,
diff --git a/src/components/Spinner/Spinner.tsx b/src/components/Spinner/Spinner.tsx
index 381fbb6..afe86f6 100644
--- a/src/components/Spinner/Spinner.tsx
+++ b/src/components/Spinner/Spinner.tsx
@@ -1,7 +1,7 @@
import { useIntl } from 'react-intl';
import styles from './Spinner.module.scss';
-const Spinner = () => {
+const Spinner = ({ message }: { message?: string }) => {
const intl = useIntl();
return (
@@ -10,10 +10,11 @@ const Spinner = () => {
<div className={styles.ball}></div>
<div className={styles.ball}></div>
<div className={styles.text}>
- {intl.formatMessage({
- defaultMessage: 'Loading...',
- description: 'Spinner: loading text',
- })}
+ {message ||
+ intl.formatMessage({
+ defaultMessage: 'Loading...',
+ description: 'Spinner: loading text',
+ })}
</div>
</div>
);