diff --git a/package-lock.json b/package-lock.json
index 59bb3583a..2e2b2a549 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -29,6 +29,7 @@
"babel-loader": "^9.1.3",
"cross-env": "^7.0.3",
"css-loader": "^7.1.2",
+ "file-loader": "^6.2.0",
"html-webpack-plugin": "^5.6.0",
"jest": "^29.7.0",
"mini-css-extract-plugin": "^2.9.1",
@@ -3988,6 +3989,16 @@
"integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==",
"dev": true
},
+ "node_modules/big.js": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -5006,6 +5017,16 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
+ "node_modules/emojis-list": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
@@ -5327,6 +5348,27 @@
"bser": "2.1.1"
}
},
+ "node_modules/file-loader": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
+ "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^4.0.0 || ^5.0.0"
+ }
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -8148,6 +8190,21 @@
"node": ">=6.11.5"
}
},
+ "node_modules/loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@@ -13919,6 +13976,12 @@
"integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==",
"dev": true
},
+ "big.js": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+ "dev": true
+ },
"binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -14639,6 +14702,12 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
+ "emojis-list": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+ "dev": true
+ },
"encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
@@ -14889,6 +14958,16 @@
"bser": "2.1.1"
}
},
+ "file-loader": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
+ "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
+ }
+ },
"fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -16910,6 +16989,17 @@
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
"dev": true
},
+ "loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ }
+ },
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
diff --git a/package.json b/package.json
index 5da2f1714..707304945 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
"babel-loader": "^9.1.3",
"cross-env": "^7.0.3",
"css-loader": "^7.1.2",
+ "file-loader": "^6.2.0",
"html-webpack-plugin": "^5.6.0",
"jest": "^29.7.0",
"mini-css-extract-plugin": "^2.9.1",
diff --git a/src/api/index.js b/src/api/index.js
index a5f073295..fbdad2cb5 100644
--- a/src/api/index.js
+++ b/src/api/index.js
@@ -1,8 +1,4 @@
class APIService {
- /**
- * @param services {Services} Менеджер сервисов
- * @param config {Object}
- */
constructor(services, config = {}) {
this.services = services;
this.config = config;
@@ -11,35 +7,25 @@ class APIService {
};
}
- /**
- * HTTP запрос
- * @param url
- * @param method
- * @param headers
- * @param options
- * @returns {Promise<{}>}
- */
- async request({ url, method = 'GET', headers = {}, ...options }) {
- if (!url.match(/^(http|\/\/)/)) url = this.config.baseUrl + url;
- const res = await fetch(url, {
- method,
- headers: { ...this.defaultHeaders, ...headers },
- ...options,
- });
- return { data: await res.json(), status: res.status, headers: res.headers };
+ setHeader(name, value = null) {
+ if (value != null) this.defaultHeaders[name] = value;
+ else delete this.defaultHeaders[name];
}
- /**
- * Установка или сброс заголовка
- * @param name {String} Название заголовка
- * @param value {String|null} Значение заголовка
- */
- setHeader(name, value = null) {
- if (value) {
- this.defaultHeaders[name] = value;
- } else if (this.defaultHeaders[name]) {
- delete this.defaultHeaders[name];
- }
+ async request({ url, method='GET', headers={}, ...opts }) {
+ if (!/^(http|\/\/)/.test(url)) url = this.config.baseUrl + url;
+
+ const langHeader = this.services.i18n.lang;
+ const finalHeaders = {
+ ...this.defaultHeaders,
+ 'Accept-Language': langHeader,
+ ...headers,
+ };
+
+ const res = await fetch(url, { method, headers: finalHeaders, ...opts });
+ const data = await res.json();
+
+ return { data, status: res.status, headers: res.headers };
}
}
diff --git a/src/app/article/index.js b/src/app/article/index.js
index 54f037b64..7ff8bdf73 100644
--- a/src/app/article/index.js
+++ b/src/app/article/index.js
@@ -1,64 +1,71 @@
-import { memo, useCallback } from 'react';
-import { useParams } from 'react-router-dom';
-import useStore from '../../hooks/use-store';
-import useTranslate from '../../hooks/use-translate';
-import useInit from '../../hooks/use-init';
-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 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 React, { memo, useCallback } from 'react'
+import { useParams } from 'react-router-dom'
+import useStore from '../../hooks/use-store'
+import useTranslate from '../../hooks/use-translate'
+import { useArticle } from '../../hooks/use-article'
+import { useComments } from '../../hooks/use-comments'
+import PageLayout from '../../components/page-layout'
+import Head from '../../components/head'
+import Spinner from '../../components/spinner'
+import ArticleCard from '../../components/article-card'
+import LocaleSelect from '../../containers/locale-select'
+import TopHead from '../../containers/top-head'
+import Navigation from '../../containers/navigation'
+import HeadLayout from '../../components/head-layout'
+import CommentsSection from '../../components/comments-section'
function Article() {
- const store = useStore();
+ const store = useStore()
+ const { t, lang } = useTranslate()
+ const { id } = useParams()
+ const { article, waiting: artLoading } = useArticle(id)
+ const {
+ comments, waiting: comLoading,
+ replyTo, text, setText,
+ onReplyClick, onSubmit
+ } = useComments(id)
- const dispatch = useDispatch();
- // Параметры из пути /articles/:id
-
- const params = useParams();
-
- useInit(() => {
- //store.actions.article.load(params.id);
- dispatch(articleActions.load(params.id));
- }, [params.id]);
-
- const select = useSelector(
- state => ({
- article: state.article.data,
- waiting: state.article.waiting,
- }),
- shallowequal,
- ); // Нужно указать функцию для сравнения свойства объекта, так как хуком вернули объект
-
- const { t } = useTranslate();
-
- const callbacks = {
- // Добавление в корзину
- addToBasket: useCallback(_id => store.actions.basket.addToBasket(_id), [store]),
- };
+ const isAuth = Boolean(localStorage.getItem('token'))
+ const addToBasket = useCallback(
+ id => store.actions.basket.addToBasket(id),
+ [store]
+ )
return (
<>
-
+
-
-
+
+
-
-
+
+
+
+
+
{t('comments.heading')} ({comments.length})
+
+
+
+
>
- );
+ )
}
-export default memo(Article);
+export default memo(Article)
diff --git a/src/app/basket/index.js b/src/app/basket/index.js
index 52a7badfd..865f3b8e1 100644
--- a/src/app/basket/index.js
+++ b/src/app/basket/index.js
@@ -2,7 +2,7 @@ import { memo, useCallback } from 'react';
import { useDispatch, useStore as useStoreRedux } 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';
@@ -21,11 +21,10 @@ function Basket() {
}));
const callbacks = {
- // Удаление из корзины
+
removeFromBasket: useCallback(_id => store.actions.basket.removeFromBasket(_id), [store]),
- // Закрытие любой модалки
+
closeModal: useCallback(() => {
- //store.actions.modals.close();
dispatch(modalsActions.close());
}, [store]),
};
diff --git a/src/app/index.js b/src/app/index.js
index c54c2afc5..3014dc1e6 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';
@@ -21,6 +19,7 @@ function App() {
await store.actions.session.remind();
});
+
const activeModal = useSelectorRedux(state => state.modals.name);
return (
diff --git a/src/app/profile/index.js b/src/app/profile/index.js
index 166d127fb..4b6ea76c6 100644
--- a/src/app/profile/index.js
+++ b/src/app/profile/index.js
@@ -24,7 +24,7 @@ function Profile() {
waiting: state.profile.waiting,
}));
- const { t } = useTranslate();
+const { t, lang } = useTranslate();
return (
<>
@@ -37,7 +37,7 @@ function Profile() {
-
+
>
diff --git a/src/assets/icon/cart_empty.png b/src/assets/icon/cart_empty.png
new file mode 100644
index 000000000..ab1c4b449
Binary files /dev/null and b/src/assets/icon/cart_empty.png differ
diff --git a/src/components/article-card/index.js b/src/components/article-card/index.js
index d636d9f88..68875cfef 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.dev')}:
{article.madeIn?.title} ({article.madeIn?.code})
-
Категория:
+
{t('article.product.cat')}:
{article.category?.title}
-
Год выпуска:
+
{t('article.country.dev')}:
{article.edition}
-
Цена:
+
{t('article.price')}:
{numberFormat(article.price)} ₽