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
16 changes: 16 additions & 0 deletions src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,18 @@ class APIService {
this.defaultHeaders = {
'Content-Type': 'application/json',
};
this.unsubscribe = this.services.i18n.subscribe((locale) => {
this.updateLanguageHeader(locale);
});

this.updateLanguageHeader(this.services.i18n.getLocale());

}
updateLanguageHeader(locale) {
this.defaultHeaders['Accept-Language'] = locale || 'ru';
}


/**
* HTTP запрос
* @param url
Expand Down Expand Up @@ -41,6 +51,12 @@ class APIService {
delete this.defaultHeaders[name];
}
}
destroy() {
if (this.unsubscribe) {
this.unsubscribe();
this.unsubscribe = null;
}
}
}

export default APIService;
18 changes: 17 additions & 1 deletion src/app/article/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import TopHead from '../../containers/top-head';
import { useDispatch, useSelector } from 'react-redux';
import shallowequal from 'shallowequal';
import articleActions from '../../store-redux/article/actions';
import commentsActions from '../../store-redux/comments/actions';
import HeadLayout from '../../components/head-layout';
import CommentsWrapper from '../../components/comments-wrapper';

function Article() {
const store = useStore();
Expand All @@ -26,11 +28,14 @@ function Article() {
useInit(() => {
//store.actions.article.load(params.id);
dispatch(articleActions.load(params.id));
dispatch(commentsActions.load(params.id));
}, [params.id]);

const select = useSelector(
state => ({
article: state.article.data,
comments: state.comments.data,
commentsWaiting: state.comments.waiting,
waiting: state.article.waiting,
}),
shallowequal,
Expand All @@ -42,7 +47,6 @@ function Article() {
// Добавление в корзину
addToBasket: useCallback(_id => store.actions.basket.addToBasket(_id), [store]),
};

return (
<>
<HeadLayout>
Expand All @@ -56,6 +60,18 @@ function Article() {
<Spinner active={select.waiting}>
<ArticleCard article={select.article} onAdd={callbacks.addToBasket} t={t} />
</Spinner>
{select.commentsWaiting ? (
<div className="loading">
<span>Загрузка комментариев...</span>
</div>
) : (
<CommentsWrapper
t={t}
_id={select.article._id}
_type={select.article._type}
comments={select.comments}
/>
)}
</PageLayout>
</>
);
Expand Down
1 change: 1 addition & 0 deletions src/app/login/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import useInit from '../../hooks/use-init';
import HeadLayout from '../../components/head-layout';
import Form from '../../components/form';


function Login() {
const { t } = useTranslate();
const location = useLocation();
Expand Down
8 changes: 4 additions & 4 deletions src/components/article-card/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@ function ArticleCard(props) {
<div className={cn('description')}>{article.description}</div>
<div className={cn('prop-wrapper')}>
<div className={cn('prop')}>
<div className={cn('label')}>Страна производитель:</div>
<div className={cn('label')}>{t(`product.country`)}:</div>
<div className={cn('value')}>
{article.madeIn?.title} ({article.madeIn?.code})
</div>
</div>
<div className={cn('prop')}>
<div className={cn('label')}>Категория:</div>
<div className={cn('label')}>{t(`product.category`)}:</div>
<div className={cn('value')}>{article.category?.title}</div>
</div>
<div className={cn('prop')}>
<div className={cn('label')}>Год выпуска:</div>
<div className={cn('label')}>{t(`product.year`)}:</div>
<div className={cn('value')}>{article.edition}</div>
</div>
</div>
<div className={cn('prop', { size: 'big' })}>
<div className={cn('label')}>Цена:</div>
<div className={cn('label')}>{t(`product.price`)}:</div>
<div className={cn('value')}>{numberFormat(article.price)} ₽</div>
</div>
<Button style="primary" onClick={() => onAdd(article._id)} title={t('article.add')} />
Expand Down
95 changes: 95 additions & 0 deletions src/components/comment-container/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { memo, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import CommentForm from '../comment-form';
import CommentItem from '../comment-item';

function CommentContainer(props) {
const {
LinkToLogin,
treeLevel = 0,
commentIdFormVisible = '',
setCommentIdFormVisible = () => {},
isLogin = false,
lastChild = false,
userId = '',
error = '',
formRef = '',
createComment = () => {},
} = props;

if (!props.comment) return null;

const { author, dateCreate, text, children, _id, parent } = props.comment;
const hasChildren = children.length > 0;
const maxlevel = 15; //максимальный уровень вложенности

const commentOffset = 40 * Math.min(maxlevel, treeLevel);
const formOffset = commentOffset + (!hasChildren && commentIdFormVisible === _id ? 40 : 0);
const date = new Date(dateCreate).toLocaleString();
const isUserComment = userId === author._id;
const isShowForm =
(commentIdFormVisible === parent._id && lastChild) ||
(!hasChildren && commentIdFormVisible === _id);

return (
<>
<CommentItem
setCommentIdFormVisible={setCommentIdFormVisible}
text={text}
date={date}
authorName={author.profile.name}
isUserComment={isUserComment}
commentOffset={commentOffset}
_id={_id}
/>
{hasChildren &&
children.map((child, index) => {
return (
<CommentContainer
isLogin={isLogin}
key={child._id}
setCommentIdFormVisible={setCommentIdFormVisible}
commentIdFormVisible={commentIdFormVisible}
comment={child}
treeLevel={treeLevel + 1}
LinkToLogin={LinkToLogin}
lastChild={!!(index === children.length - 1)}
userId={userId}
formRef={formRef}
createComment={createComment}
/>
);
})}
<div ref={isShowForm ? props.formRef : null} style={{ marginLeft: `${formOffset}px` }}>
{isShowForm &&
(isLogin ? (
<CommentForm
_id={commentIdFormVisible}
_type="comment"
secondButtonTitle="Отмена"
setCommentIdFormVisible={setCommentIdFormVisible}
title="Новый ответ"
error={error}
createComment={createComment}
/>
) : (
<LinkToLogin />
))}
</div>

</>
);
}

CommentContainer.propTypes = {
author: PropTypes.string,
dateCreate: PropTypes.string,
parent: PropTypes.shape({
_id: PropTypes.string,
type: PropTypes.string,
}),
text: PropTypes.string,
_id: PropTypes.string,
};

export default memo(CommentContainer);
41 changes: 41 additions & 0 deletions src/components/comment-form/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { memo, useState } from 'react';
import Form from '../form';
import Input from '../input';
import PropTypes from 'prop-types';

function CommentForm({
title = `Новый комментарий`,
setCommentIdFormVisible,
secondButtonTitle = '',
_id = '',
_type = '',
error = '',
createComment = () => {},
}) {
const [inputText, setInputText] = useState('');
return (
<>
<Form
title={title}
submitTitle={`Отправить`}
secondButtonTitle={secondButtonTitle}
secondButtonFunc={setCommentIdFormVisible}
onSubmit={event => createComment(event, { text: inputText, _id: _id, _type: _type })}
>
<Input theme={`commentForm`} onChange={setInputText} />
{error && <span className="errorMessage">{error}</span>}
</Form>
</>
);
}

CommentForm.propTypes = {
title: PropTypes.string,
setCommentIdFormVisible: PropTypes.func,
secondButtonTitle: PropTypes.string,
_id: PropTypes.string,
_type: PropTypes.string,
error: PropTypes.string,
};

export default memo(CommentForm);
39 changes: 39 additions & 0 deletions src/components/comment-item/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { memo } from 'react';
import PropTypes from 'prop-types';
import { cn as bem } from '@bem-react/classname';
import './style.css';

function CommentItem({
commentOffset = 0,
isUserComment = false,
authorName = '',
date = '',
text = '',
setCommentIdFormVisible = () => {},
_id = '',
}) {
const cn = bem('CommentItem');
return (
<div className={cn()} style={{ marginLeft: `${commentOffset}px` }}>
<div className={cn('header')}>
<span className={cn('author', { userComment: isUserComment })}>{authorName}</span>
<span className={cn('date')}>{date}</span>
</div>
<div className={cn('content')}>{text}</div>
<span onClick={() => setCommentIdFormVisible(_id)} className={cn('mention')}>
Ответить
</span>
</div>
);
}
CommentItem.propTypes = {
setCommentIdFormVisible: PropTypes.func.isRequired,
text: PropTypes.string.isRequired,
date: PropTypes.string.isRequired,
authorName: PropTypes.string.isRequired,
isUserComment: PropTypes.bool.isRequired,
commentOffset: PropTypes.number.isRequired,
_id: PropTypes.string.isRequired,
};

export default memo(CommentItem);
61 changes: 61 additions & 0 deletions src/components/comment-item/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
.CommentItem {
display: flex;
flex-direction: column;
gap: 6px;
}
.CommentItem-author {
font-family: 'Golos Text';
font-size: 12px;
font-weight: 700;
line-height: 18px;
letter-spacing: 0%;
}
.CommentItem-author_userComment {
color: #4B5563;

}
.CommentItem-date {
font-family: 'Golos Text';
font-weight: 400;
font-size: 12px;
line-height: 18px;
letter-spacing: 0%;
color: #666666;
}
.CommentItem-header {
gap: 15px;
display: flex;
}
.CommentItem-content {
overflow-wrap: break-word;
font-family: 'Golos Text';
font-weight: 400;
font-size: 14px;
line-height: 20px;
letter-spacing: 0%;
}
.CommentItem-mention {
font-family: 'Montserrat Alternates';
font-weight: 700;
font-size: 12px;
line-height: 18px;
letter-spacing: 0%;
color: #6b4acb;
cursor: pointer;
}
.errorMessage {
font-family: 'Golos Text';
font-size: 18px;
font-weight: 400;
line-height: 18px;
letter-spacing: 0%;
color: red;
}
.loginToAllowComment {
font-family: 'Golos Text';
font-weight: 400;
font-size: 16px;
line-height: 22px;
letter-spacing: 0%;
}

Loading