From bd9c9ae7e2ae973969569dd434836de9f38b07d4 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Tue, 7 Nov 2023 16:55:58 +0100 Subject: refactor(components): split Comment component into 3 components * add ApprovedComment, PendingComment and ReplyCommentForm components * let consumer handle reply form visibility * move structured data into article page (each article already has the comments data and already handle json ltd schema so I prefered to move the schema in the final consumer instead of adding a script element foreach comment) --- src/components/organisms/layout/comment.fixture.ts | 35 ---- .../organisms/layout/comment.module.scss | 78 --------- .../organisms/layout/comment.stories.tsx | 115 ------------- src/components/organisms/layout/comment.test.tsx | 44 ----- src/components/organisms/layout/comment.tsx | 189 --------------------- .../organisms/layout/comments-list.fixture.ts | 2 + .../organisms/layout/comments-list.module.scss | 3 + .../organisms/layout/comments-list.test.tsx | 3 +- src/components/organisms/layout/comments-list.tsx | 88 ++++++++-- src/components/organisms/layout/index.ts | 1 - 10 files changed, 80 insertions(+), 478 deletions(-) delete mode 100644 src/components/organisms/layout/comment.fixture.ts delete mode 100644 src/components/organisms/layout/comment.module.scss delete mode 100644 src/components/organisms/layout/comment.stories.tsx delete mode 100644 src/components/organisms/layout/comment.test.tsx delete mode 100644 src/components/organisms/layout/comment.tsx create mode 100644 src/components/organisms/layout/comments-list.module.scss (limited to 'src/components/organisms/layout') diff --git a/src/components/organisms/layout/comment.fixture.ts b/src/components/organisms/layout/comment.fixture.ts deleted file mode 100644 index 84aa20e..0000000 --- a/src/components/organisms/layout/comment.fixture.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { UserCommentProps } from './comment'; - -export const author = { - avatar: { - alt: 'Author avatar', - height: 480, - src: 'http://placeimg.com/640/480', - width: 640, - }, - name: 'Armand', - website: 'https://www.armandphilippot.com/', -}; - -export const content = - 'Harum aut cumque iure fugit neque sequi cupiditate repudiandae laudantium. Ratione aut assumenda qui illum voluptas accusamus quis officiis exercitationem. Consectetur est harum eius perspiciatis officiis nihil. Aut corporis minima debitis adipisci possimus debitis et.'; - -export const date = '2021-04-03 23:04:24'; - -export const meta = { - author, - date, -}; - -export const id = 5; - -export const saveComment = () => undefined; - -export const data: UserCommentProps = { - approved: true, - content, - id, - meta, - parentId: 0, - onSubmit: saveComment, -}; diff --git a/src/components/organisms/layout/comment.module.scss b/src/components/organisms/layout/comment.module.scss deleted file mode 100644 index 096f4c4..0000000 --- a/src/components/organisms/layout/comment.module.scss +++ /dev/null @@ -1,78 +0,0 @@ -@use "../../../styles/abstracts/functions" as fun; -@use "../../../styles/abstracts/mixins" as mix; -@use "../../../styles/abstracts/placeholders"; - -.avatar { - img { - border-radius: fun.convert-px(3); - box-shadow: - 0 0 0 fun.convert-px(1) var(--color-shadow-light), - fun.convert-px(2) fun.convert-px(2) 0 fun.convert-px(1) - var(--color-shadow); - } -} - -.author { - color: var(--color-primary-darker); - font-family: var(--font-family-regular); - font-size: var(--font-size-md); - font-weight: 600; - text-align: center; - text-shadow: none; -} - -.body { - overflow-wrap: break-word; - - :global { - a { - @extend %link; - - &[hreflang], - &.download, - &.external { - @extend %link-with-icon; - } - - &[hreflang] { - @extend %link-with-lang; - } - - &[hreflang]:not(.download, .external) { - --is-icon-hidden: ""; - } - - &.download { - @extend %download-link; - } - - &.external { - @extend %external-link; - } - - &.download, - &.external { - &:not([hreflang]) { - --is-lang-hidden: ""; - } - } - - &.external.download { - @extend %external-download-link; - } - } - } -} - -.form { - &__wrapper { - margin-top: var(--spacing-sm); - } - - &__heading { - width: fit-content; - margin: 0 auto var(--spacing-md) auto; - } - - margin-inline: auto; -} diff --git a/src/components/organisms/layout/comment.stories.tsx b/src/components/organisms/layout/comment.stories.tsx deleted file mode 100644 index 7426fc3..0000000 --- a/src/components/organisms/layout/comment.stories.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import type { ComponentMeta, ComponentStory } from '@storybook/react'; -import { UserComment } from './comment'; -import { data } from './comment.fixture'; - -const saveComment = async () => { - /** Do nothing. */ -}; - -/** - * Comment - Storybook Meta - */ -export default { - title: 'Organisms/Layout/Comment', - component: UserComment, - args: { - canReply: true, - onSubmit: saveComment, - }, - argTypes: { - author: { - description: 'The author data.', - type: { - name: 'object', - required: true, - value: {}, - }, - }, - canReply: { - control: { - type: 'boolean', - }, - description: 'Enable or disable the reply button.', - table: { - category: 'Options', - defaultValue: { summary: true }, - }, - type: { - name: 'boolean', - required: false, - }, - }, - content: { - control: { - type: 'text', - }, - description: 'The comment body.', - type: { - name: 'string', - required: true, - }, - }, - id: { - control: { - type: 'number', - }, - description: 'The comment id.', - type: { - name: 'number', - required: true, - }, - }, - parentId: { - control: { - type: null, - }, - description: 'The parent id if it is a reply.', - type: { - name: 'number', - required: false, - }, - }, - publication: { - description: 'The publication date.', - type: { - name: 'object', - required: true, - value: {}, - }, - }, - onSubmit: { - control: { - type: null, - }, - description: 'A callback function to save the comment form data.', - table: { - category: 'Events', - }, - type: { - name: 'function', - required: true, - }, - }, - }, -} as ComponentMeta; - -const Template: ComponentStory = (args) => ( - -); - -/** - * Layout Stories - Approved - */ -export const Approved = Template.bind({}); -Approved.args = { - ...data, -}; - -/** - * Layout Stories - Unapproved - */ -export const Unapproved = Template.bind({}); -Unapproved.args = { - ...data, - approved: false, -}; diff --git a/src/components/organisms/layout/comment.test.tsx b/src/components/organisms/layout/comment.test.tsx deleted file mode 100644 index 0e0ea3a..0000000 --- a/src/components/organisms/layout/comment.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import { render, screen as rtlScreen } from '../../../../tests/utils'; -import { UserComment } from './comment'; -import { author, data, id } from './comment.fixture'; - -describe('UserComment', () => { - it('renders an avatar', () => { - render(); - expect( - rtlScreen.getByRole('img', { name: author.avatar.alt }) - ).toBeInTheDocument(); - }); - - it('renders the author website url', () => { - render(); - expect(rtlScreen.getByRole('link', { name: author.name })).toHaveAttribute( - 'href', - author.website - ); - }); - - it('renders a permalink to the comment', () => { - render(); - expect( - rtlScreen.getByRole('link', { - name: /\sat\s/, - }) - ).toHaveAttribute('href', `#comment-${id}`); - }); - - it('renders a reply button', () => { - render(); - expect( - rtlScreen.getByRole('button', { name: 'Reply' }) - ).toBeInTheDocument(); - }); - - it('does not render a reply button', () => { - render(); - expect( - rtlScreen.queryByRole('button', { name: 'Reply' }) - ).not.toBeInTheDocument(); - }); -}); diff --git a/src/components/organisms/layout/comment.tsx b/src/components/organisms/layout/comment.tsx deleted file mode 100644 index b55bb3d..0000000 --- a/src/components/organisms/layout/comment.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import NextImage from 'next/image'; -import Script from 'next/script'; -import type { FC } from 'react'; -import { useIntl } from 'react-intl'; -import type { Comment as CommentSchema, WithContext } from 'schema-dts'; -import type { SingleComment } from '../../../types'; -import { useSettings, useToggle } from '../../../utils/hooks'; -import { Button, Heading, Link, Time } from '../../atoms'; -import { - Card, - CardActions, - CardBody, - CardCover, - CardFooter, - CardHeader, - CardMeta, - CardTitle, -} from '../../molecules'; -import { CommentForm, type CommentFormProps } from '../forms'; -import styles from './comment.module.scss'; - -export type UserCommentProps = Pick< - SingleComment, - 'approved' | 'content' | 'id' | 'meta' | 'parentId' -> & - Pick & { - /** - * Enable or disable the reply button. Default: true. - */ - canReply?: boolean; - }; - -/** - * UserComment component - * - * Render a single comment. - */ -export const UserComment: FC = ({ - approved, - canReply = true, - content, - id, - meta, - onSubmit, - parentId, - ...props -}) => { - const intl = useIntl(); - const { website } = useSettings(); - const [isReplying, toggleIsReplying] = useToggle(false); - - if (!approved) { - return ( -
- {intl.formatMessage({ - defaultMessage: 'This comment is awaiting moderation...', - description: 'Comment: awaiting moderation', - id: '6a1Uo6', - })} -
- ); - } - - const { author, date } = meta; - - 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 = { - '@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 ( - <> -