Files
frontend-app-discussions/src/discussions/posts/post/PostHeader.jsx
2026-01-16 19:22:25 +02:00

177 lines
5.2 KiB
JavaScript

import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import { Avatar, Badge, Icon } from '@openedx/paragon';
import { Question } from '@openedx/paragon/icons';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { AvatarOutlineAndLabelColors, ThreadType } from '../../../data/constants';
import { AuthorLabel } from '../../common';
import { useAlertBannerVisible } from '../../data/hooks';
import { selectAuthorAvatar } from '../data/selectors';
import messages from './messages';
export const PostAvatar = React.memo(({
author, postType, authorLabel, fromPostLink, read, postUsers,
}) => {
const outlineColor = AvatarOutlineAndLabelColors[authorLabel];
const authorAvatars = useSelector(selectAuthorAvatar(author));
const avatarSize = useMemo(() => {
let size = '2rem';
if (postType === ThreadType.DISCUSSION && !fromPostLink) {
size = '2rem';
} else if (postType === ThreadType.QUESTION) {
size = '1.5rem';
}
return size;
}, [postType]);
const avatarSpacing = useMemo(() => {
let spacing = 'mr-3 ';
if (postType === ThreadType.DISCUSSION && fromPostLink) {
spacing += 'pt-2 ml-0.5';
} else if (postType === ThreadType.DISCUSSION) {
spacing += 'ml-0.5 mt-0.5';
}
return spacing;
}, [postType]);
const profileImage = getConfig()?.ENABLE_PROFILE_IMAGE === 'true'
? Object.values(postUsers ?? {})[0]?.profile?.image
: null;
return (
<div className={avatarSpacing}>
{postType === ThreadType.QUESTION && (
<Icon
src={Question}
className={classNames('position-absolute rounded-circle question-icon-size', {
'question-icon-position': fromPostLink, 'bg-white': !read, 'bg-light-300': read,
})}
/>
)}
<Avatar
className={classNames('border-0 mt-1', {
[`outline-${outlineColor}`]: outlineColor,
'outline-anonymous': !outlineColor,
'mt-3 ml-2': postType === ThreadType.QUESTION && fromPostLink,
'mt-2.5': postType === ThreadType.QUESTION && !fromPostLink,
'avarat-img-position mt-17px': postType === ThreadType.QUESTION,
})}
style={{
height: avatarSize,
width: avatarSize,
}}
src={profileImage?.hasImage ? profileImage?.imageUrlSmall : authorAvatars?.imageUrlSmall}
alt={author}
/>
</div>
);
});
PostAvatar.propTypes = {
author: PropTypes.string.isRequired,
postType: PropTypes.string.isRequired,
authorLabel: PropTypes.string,
fromPostLink: PropTypes.bool,
read: PropTypes.bool,
postUsers: PropTypes.shape({}).isRequired,
};
PostAvatar.defaultProps = {
authorLabel: null,
fromPostLink: false,
read: false,
};
const PostHeader = ({
abuseFlagged,
author,
authorLabel,
closed,
createdAt,
hasEndorsed,
lastEdit,
title,
postType,
preview,
postUsers,
}) => {
const intl = useIntl();
const showAnsweredBadge = preview && hasEndorsed && postType === ThreadType.QUESTION;
const authorLabelColor = AvatarOutlineAndLabelColors[authorLabel];
const hasAnyAlert = useAlertBannerVisible({
author, abuseFlagged, lastEdit, closed,
});
return (
<div className={classNames('d-flex flex-fill mw-100', { 'mt-10px': hasAnyAlert && !preview })}>
<div className="flex-shrink-0">
<PostAvatar postType={postType} author={author} authorLabel={authorLabel} postUsers={postUsers} />
</div>
<div className="align-items-center d-flex flex-row">
<div className="d-flex flex-column justify-content-start mw-100">
{preview ? (
<div className="h4 d-flex align-items-center pb-0 mb-0 flex-fill">
<div className="flex-fill text-truncate" role="heading" aria-level="1">
{title}
</div>
{showAnsweredBadge
&& <Badge variant="success">{intl.formatMessage(messages.answered)}</Badge>}
</div>
) : (
<h5
className="mb-0 font-style"
style={{ lineHeight: '21px' }}
aria-level="1"
tabIndex="-1"
accessKey="h"
>
{title}
</h5>
)}
<AuthorLabel
author={author || intl.formatMessage(messages.anonymous)}
authorLabel={authorLabel}
labelColor={authorLabelColor && `text-${authorLabelColor}`}
linkToProfile
postCreatedAt={createdAt}
postOrComment
/>
</div>
</div>
</div>
);
};
PostHeader.propTypes = {
preview: PropTypes.bool,
hasEndorsed: PropTypes.bool.isRequired,
postType: PropTypes.string.isRequired,
authorLabel: PropTypes.string,
author: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired,
abuseFlagged: PropTypes.bool,
lastEdit: PropTypes.shape({
reason: PropTypes.string,
}),
closed: PropTypes.bool,
postUsers: PropTypes.shape({}).isRequired,
};
PostHeader.defaultProps = {
authorLabel: null,
preview: false,
abuseFlagged: false,
lastEdit: {},
closed: false,
};
export default React.memo(PostHeader);