diff --git a/package.json b/package.json
index 5da2f1714..57922c0ab 100644
--- a/package.json
+++ b/package.json
@@ -47,4 +47,4 @@
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.1.0"
}
-}
+}
\ No newline at end of file
diff --git a/src/app/article/index.js b/src/app/article/index.js
index 54f037b64..69cc216c6 100644
--- a/src/app/article/index.js
+++ b/src/app/article/index.js
@@ -1,60 +1,203 @@
-import { memo, useCallback } from 'react';
+import { memo, useCallback, useMemo } from 'react';
import { useParams } from 'react-router-dom';
+import { useDispatch, useSelector as useSelectorRedux } from 'react-redux';
+
+import shallowequal from 'shallowequal';
+
import useStore from '../../hooks/use-store';
import useTranslate from '../../hooks/use-translate';
import useInit from '../../hooks/use-init';
+import useSelector from '../../hooks/use-selector';
+
+import articleActions from '../../store-redux/article/actions';
+import commentsActions from '../../store-redux/comments/actions';
+import userCommentsAction from '../../store-redux/user-comment/actions';
+
import PageLayout from '../../components/page-layout';
import Head from '../../components/head';
-import Navigation from '../../containers/navigation';
-import Spinner from '../../components/spinner';
import ArticleCard from '../../components/article-card';
+import Spinner from '../../components/spinner';
+import HeadLayout from '../../components/head-layout';
+import ArticleComments from '../../components/article-comments';
+import ArticleForm from '../../components/article-form';
+import ArticleAuthMessage from '../../components/article-auth-message';
+import Textarea from '../../components/textarea';
+
+import Navigation from '../../containers/navigation';
import LocaleSelect from '../../containers/locale-select';
import TopHead from '../../containers/top-head';
-import { useDispatch, useSelector } from 'react-redux';
-import shallowequal from 'shallowequal';
-import articleActions from '../../store-redux/article/actions';
-import HeadLayout from '../../components/head-layout';
+
+import { textsTreeToList } from '../../utils/texts-tree-to-list';
+import listToTree from '../../utils/list-to-tree';
+import { getLastCommentChildrenId } from '../../utils/get-last-comment-children-id';
function Article() {
const store = useStore();
-
const dispatch = useDispatch();
- // Параметры из пути /articles/:id
-
const params = useParams();
+ const { t, lang } = useTranslate();
useInit(() => {
- //store.actions.article.load(params.id);
+ dispatch(commentsActions.load(params.id));
dispatch(articleActions.load(params.id));
- }, [params.id]);
+ if (userComment.isOpenFormInComments) {
+ dispatch(userCommentsAction.resetForm());
+ }
+ }, [params.id, lang]);
- const select = useSelector(
- state => ({
- article: state.article.data,
- waiting: state.article.waiting,
- }),
+ const selectUser = useSelector(state => ({
+ isUserAuth: state.session.exists,
+ userId: state.session.user._id,
+ userName: state.session.user.profile?.name,
+ token: state.session.token,
+ }));
+
+ const { article, comments, userComment, ...state } = useSelectorRedux(
+ state => ({ ...state }),
shallowequal,
- ); // Нужно указать функцию для сравнения свойства объекта, так как хуком вернули объект
+ );
- const { t } = useTranslate();
+ useInit(() => {
+ dispatch(userCommentsAction.resetUserCommentStore());
+ dispatch(userCommentsAction.init(selectUser.userId, selectUser.token, params.id));
+ }, [selectUser.token]);
const callbacks = {
// Добавление в корзину
addToBasket: useCallback(_id => store.actions.basket.addToBasket(_id), [store]),
+ onCloseFormInComments: useCallback(() => {
+ const commentDefaultData = {
+ parent: {
+ _id: params.id,
+ _type: 'article',
+ },
+ commentAuthorNick: '',
+ userComment: '',
+ isOpenFormInComments: false,
+ lastIdFromCommentTree: '',
+ };
+ dispatch(userCommentsAction.setCommentsData(commentDefaultData));
+ }, []),
+ onChangeCommentData: useCallback(
+ commentData => {
+ const { lastItemId, lastChildFromTree } = getLastCommentChildrenId(
+ commentData.parent._id,
+ comments.data.items,
+ );
+ const data = {
+ ...commentData,
+ isOpenFormInComments: true,
+ lastIdFromCommentTree: lastChildFromTree,
+ };
+
+ dispatch(userCommentsAction.setCommentsData(data));
+ },
+ [comments.data.items],
+ ),
+ onChangeCommentMessage: useCallback(value => {
+ dispatch(userCommentsAction.setUserMessage(value));
+ }, []),
+ onSubmit: useCallback(
+ e => {
+ e.preventDefault();
+ const data = {
+ _id: userComment.userId,
+ text: userComment.userComment.trim(),
+ parent: {
+ ...userComment.parent,
+ },
+ token: userComment.userToken,
+ };
+
+
+ if (data.text.trim()) {
+ dispatch(commentsActions.addComment(data, selectUser.userName));
+ }
+
+ if (!article.waiting) {
+ callbacks.onCloseFormInComments();
+ }
+ },
+ [userComment],
+ ),
};
+ const options = {
+ comments: useMemo(
+ () => [
+ ...textsTreeToList(
+ listToTree(comments.data.items || [], '_id', 'parent'),
+ (item, count) => ({
+ ...item,
+ paddingL: Math.floor(40 * count),
+ }),
+ ),
+ ],
+ [comments.data.items],
+ ),
+ };
+
+ const disabledBtn = article.waiting || !userComment.userComment.trim();
+
return (
<>
-
+
-
-
+
+
+
+
+ {userComment.isOpenFormInComments &&
+ (selectUser.isUserAuth ? (
+
+
+
+ ) : (
+
+ ))}
+
+ {!userComment.isOpenFormInComments &&
+ (selectUser.isUserAuth ? (
+
+
+
+ ) : (
+
+ ))}
+
>
diff --git a/src/app/basket/index.js b/src/app/basket/index.js
index 52a7badfd..e14187817 100644
--- a/src/app/basket/index.js
+++ b/src/app/basket/index.js
@@ -1,8 +1,7 @@
import { memo, useCallback } from 'react';
-import { useDispatch, useStore as useStoreRedux } from 'react-redux';
+import { useDispatch } from 'react-redux';
import useStore from '../../hooks/use-store';
import useSelector from '../../hooks/use-selector';
-import useInit from '../../hooks/use-init';
import useTranslate from '../../hooks/use-translate';
import ItemBasket from '../../components/item-basket';
import List from '../../components/list';
@@ -13,6 +12,7 @@ import modalsActions from '../../store-redux/modals/actions';
function Basket() {
const store = useStore();
const dispatch = useDispatch();
+ const { t } = useTranslate();
const select = useSelector(state => ({
list: state.basket.list,
@@ -30,8 +30,6 @@ function Basket() {
}, [store]),
};
- const { t } = useTranslate();
-
const renders = {
itemBasket: useCallback(
item => (
diff --git a/src/app/index.js b/src/app/index.js
index c54c2afc5..5b3e3a955 100644
--- a/src/app/index.js
+++ b/src/app/index.js
@@ -1,6 +1,4 @@
-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';
import useInit from '../hooks/use-init';
import Main from './main';
@@ -10,16 +8,25 @@ import Login from './login';
import Profile from './profile';
import Protected from '../containers/protected';
import { useSelector as useSelectorRedux } from 'react-redux';
+import useServices from '../hooks/use-services';
+import useTranslate from '../hooks/use-translate';
/**
* Приложение
* @returns {React.ReactElement}
*/
function App() {
+ const services = useServices();
+ const { lang } = useTranslate();
const store = useStore();
+
useInit(async () => {
await store.actions.session.remind();
- });
+ }, [services.i18n]);
+
+ useInit(async () => {
+ await store.actions.basket.updateBasketListAnotherLang();
+ }, [lang]);
const activeModal = useSelectorRedux(state => state.modals.name);
diff --git a/src/app/login/index.js b/src/app/login/index.js
index 32b86daa4..7a4c856dc 100644
--- a/src/app/login/index.js
+++ b/src/app/login/index.js
@@ -34,7 +34,6 @@ function Login() {
login: '',
password: '',
});
-
const callbacks = {
// Колбэк на ввод в элементах формы
onChange: useCallback((value, name) => {
diff --git a/src/app/main/index.js b/src/app/main/index.js
index d9fb1c6c2..054be0a27 100644
--- a/src/app/main/index.js
+++ b/src/app/main/index.js
@@ -13,16 +13,16 @@ import HeadLayout from '../../components/head-layout';
function Main() {
const store = useStore();
+ const { t, lang } = useTranslate();
useInit(
async () => {
await Promise.all([store.actions.catalog.initParams(), store.actions.categories.load()]);
},
- [],
+ [lang],
true,
);
- const { t } = useTranslate();
return (
<>
diff --git a/src/app/profile/index.js b/src/app/profile/index.js
index 166d127fb..76043ab05 100644
--- a/src/app/profile/index.js
+++ b/src/app/profile/index.js
@@ -37,7 +37,7 @@ function Profile() {
-
+
>
diff --git a/src/components/article-auth-message/index.js b/src/components/article-auth-message/index.js
new file mode 100644
index 000000000..cdf01285d
--- /dev/null
+++ b/src/components/article-auth-message/index.js
@@ -0,0 +1,33 @@
+import {memo, useCallback} from "react";
+import { Link, useLocation, useNavigate } from 'react-router-dom';
+
+import './style.css';
+import PropTypes from 'prop-types';
+
+function ArticleAuthMessage({ t = text => text }) {
+ const navigate = useNavigate();
+ const location = useLocation();
+
+
+
+ const callbacks = {
+ onSignIn: useCallback(() => {
+ navigate('/login', { state: { back: location.pathname } });
+ }, [location.pathname]),
+ }
+
+ return (
+
+ );
+}
+
+ArticleAuthMessage.propTypes = {
+ t: PropTypes.func.isRequired,
+};
+
+export default memo(ArticleAuthMessage);
diff --git a/src/components/article-auth-message/style.css b/src/components/article-auth-message/style.css
new file mode 100644
index 000000000..ef50923ab
--- /dev/null
+++ b/src/components/article-auth-message/style.css
@@ -0,0 +1,12 @@
+.ArticleAuthMessage {
+ font-family: var(--font-family);
+ font-weight: 400;
+ font-size: 16px;
+ line-height: 137%;
+ color: #1a2028;
+}
+
+.ArticleAuthMessage-link {
+ color: #6b4acb;
+ cursor: pointer;
+}
diff --git a/src/components/article-card/index.js b/src/components/article-card/index.js
index d636d9f88..2d56ed84e 100644
--- a/src/components/article-card/index.js
+++ b/src/components/article-card/index.js
@@ -13,22 +13,22 @@ function ArticleCard(props) {
{article.description}
-
Страна производитель:
+
{t('article.country')}:
{article.madeIn?.title} ({article.madeIn?.code})
-
Категория:
+
{t('article.category')}:
{article.category?.title}
-
Год выпуска:
+
{t('article.year')}:
{article.edition}
-
Цена:
+
{t("article.price")}:
{numberFormat(article.price)} ₽