Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions src/app/article/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { memo, useCallback } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import useStore from '../../hooks/use-store';
import useTranslate from '../../hooks/use-translate';
Expand All @@ -14,6 +14,8 @@ import { useDispatch, useSelector } from 'react-redux';
import shallowequal from 'shallowequal';
import articleActions from '../../store-redux/article/actions';
import HeadLayout from '../../components/head-layout';
import commentsActions from '../../store-redux/comments/actions';
import CommentsList from '../../containers/comments-list';

function Article() {
const store = useStore();
Expand All @@ -23,26 +25,32 @@ function Article() {

const params = useParams();

useInit(() => {
//store.actions.article.load(params.id);
dispatch(articleActions.load(params.id));
}, [params.id]);
useInit(
async () => {
await Promise.all([
dispatch(articleActions.load(params.id)),
dispatch(commentsActions.loadComments(params.id)),
]);
},
[params.id],

{ watchLanguage: true, backForward: true },
);

const select = useSelector(
state => ({
article: state.article.data,
waiting: state.article.waiting,
waiting: state.article.waiting || state.comments.waiting,
comments: state.comments?.data?.items || [],
commentsCount: state.comments?.data?.count || 0,
}),
shallowequal,
); // Нужно указать функцию для сравнения свойства объекта, так как хуком вернули объект

const { t } = useTranslate();

const callbacks = {
// Добавление в корзину
addToBasket: useCallback(_id => store.actions.basket.addToBasket(_id), [store]),
};

return (
<>
<HeadLayout>
Expand All @@ -55,6 +63,11 @@ function Article() {
<Navigation />
<Spinner active={select.waiting}>
<ArticleCard article={select.article} onAdd={callbacks.addToBasket} t={t} />
<CommentsList
comments={select.comments}
commentsCount={select.commentsCount}
articleId={select.article?._id}
/>
</Spinner>
</PageLayout>
</>
Expand Down
2 changes: 1 addition & 1 deletion src/app/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useCallback, useContext, useEffect, useState } from 'react';
import { Routes, Route } from 'react-router-dom';
import useSelector from '../hooks/use-selector';
import useStore from '../hooks/use-store';
Expand All @@ -17,6 +16,7 @@ import { useSelector as useSelectorRedux } from 'react-redux';
*/
function App() {
const store = useStore();

useInit(async () => {
await store.actions.session.remind();
});
Expand Down
7 changes: 6 additions & 1 deletion src/app/login/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ function Login() {
[data, location.state],
),
};
console.log(location.state);

return (
<>
Expand All @@ -69,7 +70,11 @@ function Login() {
<PageLayout>
<Navigation />
<SideLayout padding="medium">
<Form onSubmit={callbacks.onSubmit} title={t('auth.title')} submitTitle={t('auth.signIn')}>
<Form
onSubmit={callbacks.onSubmit}
title={t('auth.title')}
submitTitle={t('auth.signIn')}
>
<Field label={t('auth.login')} error={select.errors?.login}>
<Input
name="login"
Expand Down
2 changes: 1 addition & 1 deletion src/app/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function Main() {
await Promise.all([store.actions.catalog.initParams(), store.actions.categories.load()]);
},
[],
true,
{ watchLanguage: true },
);

const { t } = useTranslate();
Expand Down
104 changes: 104 additions & 0 deletions src/components/comment-item/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { memo, useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import { cn as bem } from '@bem-react/classname';
import formatDate from '../../utils/date-format';
import CommentNew from '../comment-new';
import CommentLogin from '../comment-login';
import useTranslate from '../../hooks/use-translate';
import './style.css';

function CommentItem({
comment,
onReply,
activeCommentId,
resetActiveComment,
sessionExists,
createComment,
profileName,
}) {
const cn = bem('CommentItem');
const commentNewRef = useRef(null);
const { t } = useTranslate();

const isAuthor = profileName === comment.author?.profile?.name;
const maxLevel = 5;
const baseMarginLeft = '40px';
const childrenMarginLeft = comment.level < maxLevel ? baseMarginLeft : '0';

useEffect(() => {
if (activeCommentId === comment._id && commentNewRef.current) {
commentNewRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}, [activeCommentId, comment._id]);

return (
<div className={cn()}>
<div className={cn('info')}>
<div className={cn('user', { authenticated: isAuthor })}>
{comment.author?.profile?.name}
</div>
<div className={cn('date')}>{formatDate(comment.dateCreate, t('comments.date'))}</div>
</div>
<div className={cn('text')}>{comment.text}</div>
<button className={cn('button')} onClick={() => onReply(comment._id)}>
{t('comments.reply')}
</button>

{comment.children && comment.children.length > 0 && (
<div className={cn('children')} style={{ marginLeft: childrenMarginLeft }}>
{comment.children.map(childComment => (
<CommentItem
key={childComment._id}
comment={childComment}
onReply={onReply}
activeCommentId={activeCommentId}
resetActiveComment={resetActiveComment}
sessionExists={sessionExists}
createComment={createComment}
profileName={profileName}
/>
))}
</div>
)}

{activeCommentId === comment._id && sessionExists ? (
<div style={{ marginLeft: baseMarginLeft }} ref={commentNewRef}>
<CommentNew
status="reply"
onSubmit={text => createComment(text, comment._id)}
onCancel={resetActiveComment}
/>
</div>
) : (
activeCommentId === comment._id && (
<div style={{ marginLeft: baseMarginLeft }} ref={commentNewRef}>
<CommentLogin />
</div>
)
)}
</div>
);
}

CommentItem.propTypes = {
comment: PropTypes.shape({
_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
level: PropTypes.number,
author: PropTypes.shape({
profile: PropTypes.shape({
name: PropTypes.string,
}),
}),
dateCreate: PropTypes.string,
text: PropTypes.string,
children: PropTypes.array,
}).isRequired,
onReply: PropTypes.func.isRequired,
activeCommentId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
resetActiveComment: PropTypes.func.isRequired,
sessionExists: PropTypes.bool.isRequired,
createComment: PropTypes.func.isRequired,
profileName: PropTypes.string,
};

export default memo(CommentItem);
37 changes: 37 additions & 0 deletions src/components/comment-item/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
.CommentItem {
margin-bottom: 16px;
}

.CommentItem-info {
display: flex;
gap: 12px;
margin-bottom: 6px;
}
.CommentItem-user {
font-size: 12px;
font-weight: 700;
line-height: 18px;
margin-left: 2px;
}
.CommentItem-user_authenticated {
color: rgb(75, 85, 99);
}
.CommentItem-text {
font-size: 14px;
font-weight: 400;
line-height: 20px;
overflow-wrap: break-word;
}
.CommentItem-description {
margin-bottom: 6px;
}
.CommentItem-date {
color: rgb(102, 102, 102);
font-size: 12px;
font-weight: 400;
line-height: 18px;
}
.CommentItem-button {
padding: 0;
color: rgb(107, 74, 203);
}
29 changes: 29 additions & 0 deletions src/components/comment-login/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { memo } from 'react';
import { cn as bem } from '@bem-react/classname';
import { useLocation, useNavigate } from 'react-router-dom';
import useTranslate from '../../hooks/use-translate';
import './style.css';

function CommentLogin() {
const cn = bem('CommentLogin');
const location = useLocation();
const navigate = useNavigate();
const { t } = useTranslate();

return (
<div className={cn()}>
<button
className={cn('link')}
onClick={() => {
navigate('/login', { state: { back: location.pathname } });
}}
>
{t('comments.unauth-first')}
</button>
<span>{t('comments.unauth-second')}</span>
</div>
);
1;
}

export default memo(CommentLogin);
13 changes: 13 additions & 0 deletions src/components/comment-login/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.CommentLogin {
margin-top: 24px;
margin-bottom: 24px;
}
.CommentLogin-link {
color: var(--primary);
font-family: var(--main-text);
font-size: 16px;
font-weight: 400;
line-height: 22px;
margin: 0;
padding: 0;
}
55 changes: 55 additions & 0 deletions src/components/comment-new/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { memo, useState } from 'react';
import { cn as bem } from '@bem-react/classname';
import Button from '../button';
import './style.css';
import PropTypes from 'prop-types';
import useTranslate from '../../hooks/use-translate';

function CommentNew({ status, onSubmit, onCancel }) {
const cn = bem('CommentNew');
const { t } = useTranslate();
const [commentText, setCommentText] = useState('');

const handleSubmit = e => {
e.preventDefault();
if (!commentText.trim()) return;
onSubmit(commentText);
setCommentText('');
};

return (
<form onSubmit={handleSubmit} className={cn()}>
<label htmlFor="new-comment" className={cn('label')}>
{status === 'global' ? t('comments.title-form-global') : t('comments.title-form')}
</label>
<textarea
className={cn('textarea')}
id="new-comment"
value={commentText}
onChange={e => setCommentText(e.target.value)}
/>
<div className={cn('action')}>
<Button title={t('comments.send')} style="primary" type="submit" />
{status !== 'global' && (
<Button
title={t('comments.reset')}
style="primary"
type="button"
onClick={() => {
setCommentText('');
if (onCancel) onCancel();
}}
/>
)}
</div>
</form>
);
}

CommentNew.propTypes = {
status: PropTypes.oneOf(['global', 'reply']).isRequired,
onSubmit: PropTypes.func.isRequired,
onCancel: PropTypes.func,
};

export default memo(CommentNew);
27 changes: 27 additions & 0 deletions src/components/comment-new/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.CommentNew {
margin-top: 24px;
margin-bottom: 24px;
}
.CommentNew-label {
display: block;
margin-bottom: 16px;
font-size: 16px;
font-weight: 700;
line-height: 22px;
}

.CommentNew-textarea {
width: 100%;
padding: 10px;
border: 1px solid var(--divider);
border-radius: 4px;
resize: none;
outline: none;
min-height: 88px;
box-sizing: border-box;
margin-bottom: 10px;
}
.CommentNew-action {
display: flex;
gap: 16px;
}
Loading