import Script from 'next/script'; import { type FC, type HTMLAttributes, type ReactNode, useRef, useState, useCallback, } from 'react'; import { useIntl } from 'react-intl'; import type { BreadcrumbList } from 'schema-dts'; import { sendComment } from '../../../services/graphql'; import type { Approved, SendCommentInput, SingleComment } from '../../../types'; import { useIsMounted } from '../../../utils/hooks'; import { Heading, Notice, type NoticeKind, Sidebar } from '../../atoms'; import { Breadcrumb, type BreadcrumbItem, type MetaData, PageFooter, type PageFooterProps, PageHeader, type PageHeaderProps, } from '../../molecules'; import { CommentForm, type CommentFormProps, CommentsList, type CommentsListProps, TableOfContents, } from '../../organisms'; import styles from './page-layout.module.scss'; /** * Check if there is at least one comment. * * @param {SingleComment[] | undefined} comments - The comments. */ const hasComments = ( comments: SingleComment[] | undefined ): comments is SingleComment[] => Array.isArray(comments) && comments.length > 0; /** * Check if meta properties are defined. * * @param {MetaData} meta - The metadata. */ const hasMeta = (meta: MetaData) => Object.values(meta).every((value) => value); type CommentStatus = { isReply: boolean; kind: NoticeKind; message: string; }; export type PageLayoutProps = { /** * True if the page accepts new comments. Default: false. */ allowComments?: boolean; /** * Set attributes to the page body. */ bodyAttributes?: HTMLAttributes; /** * Set additional classnames to the body wrapper. */ bodyClassName?: string; /** * The breadcrumb items. */ breadcrumb: BreadcrumbItem[]; /** * The breadcrumb JSON schema. */ breadcrumbSchema: BreadcrumbList['itemListElement'][]; /** * The main content of the page. */ children: ReactNode; /** * The page comments */ comments?: CommentsListProps['comments']; /** * The footer metadata. */ footerMeta?: PageFooterProps['meta']; /** * The header metadata. */ headerMeta?: PageHeaderProps['meta']; /** * The page id. */ id?: number; /** * The page introduction. */ intro?: PageHeaderProps['intro']; /** * The page title. */ title: PageHeaderProps['title']; /** * An array of widgets to put in the last sidebar. */ widgets?: ReactNode[]; /** * Show the table of contents. Default: false. */ withToC?: boolean; }; /** * PageLayout component * * Render the pages layout. */ export const PageLayout: FC = ({ children, allowComments = false, bodyAttributes, bodyClassName = '', breadcrumb, breadcrumbSchema, comments, footerMeta, headerMeta, id, intro, title, widgets, withToC = false, }) => { const intl = useIntl(); const commentsTitle = intl.formatMessage({ defaultMessage: 'Comments', description: 'PageLayout: comments title', id: '+dJU3e', }); const commentFormTitle = intl.formatMessage({ defaultMessage: 'Leave a comment', description: 'PageLayout: comment form title', id: 'kzIYoQ', }); const bodyRef = useRef(null); const isMounted = useIsMounted(bodyRef); const [commentStatus, setCommentStatus] = useState( undefined ); const isSuccessStatus = useCallback( (comment: Approved | null, isReply: boolean, isSuccess: boolean) => { if (isSuccess) { const successPrefix = intl.formatMessage({ defaultMessage: 'Thanks, your comment was successfully sent.', description: 'PageLayout: comment form success message', id: 'B290Ph', }); const successMessage = comment?.approved ? intl.formatMessage({ defaultMessage: 'It has been approved.', id: 'g3+Ahv', description: 'PageLayout: comment approved.', }) : intl.formatMessage({ defaultMessage: 'It is now awaiting moderation.', id: 'Vmj5cw', description: 'PageLayout: comment awaiting moderation', }); setCommentStatus({ isReply, kind: 'success', message: `${successPrefix} ${successMessage}`, }); return true; } const error = intl.formatMessage({ defaultMessage: 'An error occurred:', description: 'PageLayout: comment form error message', id: 'fkcTGp', }); setCommentStatus({ isReply, kind: 'error', message: error }); return false; }, [intl] ); const saveComment: CommentFormProps['saveComment'] = useCallback( async (data, reset) => { if (!id) throw new Error('Page id missing. Cannot save comment.'); const { author, comment: commentBody, email, parentId, website } = data; const isReply = !!parentId; const commentData: SendCommentInput = { author, authorEmail: email, authorUrl: website ?? '', clientMutationId: 'comment', commentOn: id, content: commentBody, parent: parentId, }; const { comment, success } = await sendComment(commentData); if (isSuccessStatus(comment, isReply, success)) reset(); }, [id, isSuccessStatus] ); return ( <>