From 9539f40d380d1a76f45f7f896453e804a3d64aaf Mon Sep 17 00:00:00 2001 From: Sergei Iu Date: Sat, 19 Apr 2025 17:05:22 +0300 Subject: [PATCH 01/15] feat: add comments and i18n service (raw) --- src/api/index.js | 16 + src/app/article/index.js | 788 +++- src/app/main/index.js | 1 + src/components/article-card/index.js | 4 +- src/components/comment-card/index.js | 111 + src/components/comment-card/style.css | 52 + src/components/comment-list/index.js | 115 + src/components/comment-list/style.css | 64 + src/config.js | 8 + src/containers/locale-select/index.js | 19 +- src/global.css | 1 + src/hooks/use-services.js | 8 +- src/hooks/use-translate.js | 27 +- src/i18n/index.js | 80 + src/i18n/translate.js | 2 +- src/index.js | 6 +- src/services.js | 12 + src/store-redux/comments/actions.js | 24 + src/store-redux/comments/reducer.js | 26 + src/store-redux/exports.js | 1 + src/store/comments/index.js | 40 + src/store/exports.js | 1 + src/store/index.js | 4 +- src/utils/date-format.js | 23 + src/utils/list-to-tree/index.spec.js | 46 + yarn.lock | 5366 +++++++++++++++++++++++++ 26 files changed, 6832 insertions(+), 13 deletions(-) create mode 100644 src/components/comment-card/index.js create mode 100644 src/components/comment-card/style.css create mode 100644 src/components/comment-list/index.js create mode 100644 src/components/comment-list/style.css create mode 100644 src/i18n/index.js create mode 100644 src/store-redux/comments/actions.js create mode 100644 src/store-redux/comments/reducer.js create mode 100644 src/store/comments/index.js create mode 100644 src/utils/date-format.js create mode 100644 yarn.lock diff --git a/src/api/index.js b/src/api/index.js index a5f073295..8cfb41a44 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -9,6 +9,17 @@ class APIService { this.defaultHeaders = { 'Content-Type': 'application/json', }; + // this.i18n = services.i18n; + // // this.languageHeader = 'ru'; + // this.setHeader('Accept-Language', this.i18n.locale); + // this.i18n.subscribe(this.updateLanguageHeader.bind(this)); + const i18n = this.services?.i18n; + if (i18n) { + this.setHeader('X-Lang', i18n.locale); + i18n.subscribe(locale => { + return this.setHeader('X-Lang', locale); + }); + } } /** @@ -41,6 +52,11 @@ class APIService { delete this.defaultHeaders[name]; } } + + updateLanguageHeader(locale) { + this.languageHeader = locale; + this.setHeader('Accept-Language', locale); + } } export default APIService; diff --git a/src/app/article/index.js b/src/app/article/index.js index 54f037b64..851166df4 100644 --- a/src/app/article/index.js +++ b/src/app/article/index.js @@ -13,7 +13,10 @@ 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 listToTree from '../../utils/list-to-tree'; +import CommentList from '../../components/comment-list'; function Article() { const store = useStore(); @@ -24,14 +27,18 @@ function Article() { const params = useParams(); useInit(() => { - //store.actions.article.load(params.id); + // store.actions.article.load(params.id); dispatch(articleActions.load(params.id)); + dispatch(commentsActions.load(params.id)); + // store.actions.comments.getCommentsById(params.id); }, [params.id]); const select = useSelector( state => ({ article: state.article.data, waiting: state.article.waiting, + comments: state.comments.data, + count: state.comments.count, }), shallowequal, ); // Нужно указать функцию для сравнения свойства объекта, так как хуком вернули объект @@ -43,6 +50,784 @@ function Article() { addToBasket: useCallback(_id => store.actions.basket.addToBasket(_id), [store]), }; + console.log('Comments in Article', select.comments); + console.log('Count comments', select.count); + // function buildCommentTree(comments) { + // // console.log('buildCommentTree', comments); + // const commentMap = {}; + // const commentTree = []; + + // // Создаем карту комментариев по их ID + // comments.forEach(comment => { + // commentMap[comment._id] = { ...comment, replies: [] }; + // }); + + // // Строим дерево комментариев + // comments.forEach(comment => { + // if (comment.parent?._type === "article") { + // commentTree.push(commentMap[comment._id]); + // } else if (comment.parent?._type === "comment") { + // const parentComment = commentMap[comment.parent._id]; + // if (parentComment) { + // parentComment.replies.push(commentMap[comment._id]); + // } + // } + // }); + + // return commentTree; + // } + + // const buildComments = buildCommentTree(comments); + + // const comments = treeToList(listToTree(select.comments), (item, level) => ({value: item._id, title: item.text })); + // console.log('Build comments', buildComments); + const comments = [ + { + "_id": "67fee024de177582e52335bb", + "text": "Первый коммент!", + "dateCreate": "2025-04-15T22:39:32.013Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "67fee064de177582e52335c2", + "text": "Второй коммент!", + "dateCreate": "2025-04-15T22:40:36.407Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "67fee07ede177582e52335c5", + "text": "Ответ на первый коммент!", + "dateCreate": "2025-04-15T22:41:02.889Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fee024de177582e52335bb", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "67ff6be2de177582e52338d7", + "text": "d", + "dateCreate": "2025-04-16T08:35:46.413Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fee07ede177582e52335c5", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "67ff953dde177582e5233a57", + "text": "jhjh\n", + "dateCreate": "2025-04-16T11:32:13.679Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "67ff9540de177582e5233a59", + "text": "jhjh\n", + "dateCreate": "2025-04-16T11:32:16.470Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "67ff95d2de177582e5233a6e", + "text": "hgthh\n", + "dateCreate": "2025-04-16T11:34:42.411Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "67ff95dede177582e5233a71", + "text": "jhjhj", + "dateCreate": "2025-04-16T11:34:54.911Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "67ff9644de177582e5233a90", + "text": "jhjj", + "dateCreate": "2025-04-16T11:36:36.379Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "67ffb9c8de177582e5233db3", + "text": "Hi\n", + "dateCreate": "2025-04-16T14:08:08.895Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67ff953dde177582e5233a57", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "67ffcbaa6bc0478e51e8e835", + "text": "GhbdTn", + "dateCreate": "2025-04-16T15:24:26.723Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "67ffcbb06bc0478e51e8e837", + "text": "ggggg", + "dateCreate": "2025-04-16T15:24:32.481Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "67ffcbc26bc0478e51e8e84a", + "text": "HELLO!", + "dateCreate": "2025-04-16T15:24:50.775Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "67ffd0ba6bc0478e51e8e890", + "text": "Ответ на это сообщение", + "dateCreate": "2025-04-16T15:46:02.067Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67ffcbc26bc0478e51e8e84a", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "67ffd1206bc0478e51e8e89c", + "text": "11", + "dateCreate": "2025-04-16T15:47:44.523Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "67ffd3056bc0478e51e8e8b8", + "text": "Хай хай", + "dateCreate": "2025-04-16T15:55:49.436Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67ffd1206bc0478e51e8e89c", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "680000966bc0478e51e8ec2a", + "text": "fffff\n", + "dateCreate": "2025-04-16T19:10:14.940Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "680006476bc0478e51e8ecf7", + "text": "ffff\n", + "dateCreate": "2025-04-16T19:34:31.501Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "6800064d6bc0478e51e8ecfa", + "text": "uuuu\n", + "dateCreate": "2025-04-16T19:34:37.987Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "680006656bc0478e51e8ecfe", + "text": "f word\n\n", + "dateCreate": "2025-04-16T19:35:01.945Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "680006b16bc0478e51e8ed04", + "text": "derf\n", + "dateCreate": "2025-04-16T19:36:17.830Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "680006bc6bc0478e51e8ed06", + "text": "dfd", + "dateCreate": "2025-04-16T19:36:28.007Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "680006476bc0478e51e8ecf7", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "680008376bc0478e51e8ed19", + "text": "Hm\n\n\n\n", + "dateCreate": "2025-04-16T19:42:47.583Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67ffb9c8de177582e5233db3", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "680008bc6bc0478e51e8ed26", + "text": "hjj", + "dateCreate": "2025-04-16T19:45:00.600Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67ff6be2de177582e52338d7", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "68008dc06bc0478e51e8f313", + "text": "dasdad", + "dateCreate": "2025-04-17T05:12:32.799Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "680094796bc0478e51e8f376", + "text": "a", + "dateCreate": "2025-04-17T05:41:13.930Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "680094916bc0478e51e8f37a", + "text": "lol", + "dateCreate": "2025-04-17T05:41:37.310Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fee024de177582e52335bb", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "6800aa406bc0478e51e8f588", + "text": "Z", + "dateCreate": "2025-04-17T07:14:08.879Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fee024de177582e52335bb", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "6800aa4f6bc0478e51e8f58b", + "text": "z", + "dateCreate": "2025-04-17T07:14:23.510Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "680008bc6bc0478e51e8ed26", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "6800ab3a6bc0478e51e8f5c3", + "text": "вфывфв", + "dateCreate": "2025-04-17T07:18:18.514Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "6800ac416bc0478e51e8f5ec", + "text": "d", + "dateCreate": "2025-04-17T07:22:41.172Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "6800ab3a6bc0478e51e8f5c3", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "6800ad286bc0478e51e8f5fe", + "text": "ф", + "dateCreate": "2025-04-17T07:26:32.697Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "6800ac416bc0478e51e8f5ec", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "6800adb46bc0478e51e8f611", + "text": "ф", + "dateCreate": "2025-04-17T07:28:52.775Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "6800064d6bc0478e51e8ecfa", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "6800adba6bc0478e51e8f616", + "text": "ф", + "dateCreate": "2025-04-17T07:28:58.717Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "680006b16bc0478e51e8ed04", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "6800c3de6bc0478e51e8f8ab", + "text": "111", + "dateCreate": "2025-04-17T09:03:26.774Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "6800aa4f6bc0478e51e8f58b", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "6800e4fe6bc0478e51e8fe19", + "text": "123", + "dateCreate": "2025-04-17T11:24:46.962Z", + "author": { + "profile": { + "name": "User №10" + }, + "_id": "67fb67dc8702ec3fc67fe388" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "6800ecc26bc0478e51e90002", + "text": "gfdgdgdfg", + "dateCreate": "2025-04-17T11:57:54.218Z", + "author": { + "profile": { + "name": "User №10" + }, + "_id": "67fb67dc8702ec3fc67fe388" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "6800edc96bc0478e51e9006f", + "text": "абракадабра", + "dateCreate": "2025-04-17T12:02:17.174Z", + "author": { + "profile": { + "name": "User №8" + }, + "_id": "67fb67dc8702ec3fc67fe386" + }, + "parent": { + "_id": "67ffd0ba6bc0478e51e8e890", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "6800f2326bc0478e51e9014f", + "text": "wwww52", + "dateCreate": "2025-04-17T12:21:06.025Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "6800f3176bc0478e51e9019f", + "text": "wwwwwasdasd", + "dateCreate": "2025-04-17T12:24:55.856Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "6800f3286bc0478e51e901ab", + "text": "asfasfasf", + "dateCreate": "2025-04-17T12:25:12.125Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "6800c3de6bc0478e51e8f8ab", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "6800f3686bc0478e51e901bb", + "text": "wwww", + "dateCreate": "2025-04-17T12:26:16.039Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "6800f3286bc0478e51e901ab", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "6800f36f6bc0478e51e901be", + "text": "dasdasd", + "dateCreate": "2025-04-17T12:26:23.350Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "6800f3736bc0478e51e901c1", + "text": "sssssss", + "dateCreate": "2025-04-17T12:26:27.764Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fb67e98702ec3fc67fef7c", + "_type": "article" + }, + "isDeleted": false + }, + { + "_id": "6800f3816bc0478e51e901c4", + "text": "asdas", + "dateCreate": "2025-04-17T12:26:41.344Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fee024de177582e52335bb", + "_type": "comment" + }, + "isDeleted": false + }, + { + "_id": "6800f3886bc0478e51e901c7", + "text": "Ответ\n", + "dateCreate": "2025-04-17T12:26:48.517Z", + "author": { + "profile": { + "name": "User №1" + }, + "_id": "67fb67dc8702ec3fc67fe37f" + }, + "parent": { + "_id": "67fee024de177582e52335bb", + "_type": "comment" + }, + "isDeleted": false + } + ]; + let buildComments = []; + console.log('Исходный List', select.comments); + if (Array.isArray(select?.comments) && select.comments.length > 0) { + buildComments = listToTree(select?.comments)[0].children; + console.log('Преобразованный List', buildComments); + } else { + console.log('Комментарии еще не загружены или пусты'); + } + return ( <> @@ -55,6 +840,7 @@ function Article() { + diff --git a/src/app/main/index.js b/src/app/main/index.js index d9fb1c6c2..ede9702ad 100644 --- a/src/app/main/index.js +++ b/src/app/main/index.js @@ -23,6 +23,7 @@ function Main() { ); const { t } = useTranslate(); + console.log('Translation function:', t); return ( <> diff --git a/src/components/article-card/index.js b/src/components/article-card/index.js index d636d9f88..edccea820 100644 --- a/src/components/article-card/index.js +++ b/src/components/article-card/index.js @@ -7,7 +7,9 @@ import './style.css'; function ArticleCard(props) { const { article, onAdd = () => {}, t = text => text } = props; + const cn = bem('ArticleCard'); + return (
{article.description}
@@ -31,7 +33,7 @@ function ArticleCard(props) {
Цена:
{numberFormat(article.price)} ₽
- + + )} + + {/* Рендерим детей только если глубина меньше MAX_DEPTH */} + {comment.children && comment.children.length > 0 && ( +
+ {comment.children.map(child => ( + handleReplyClick(child._id)} + setAuthMessageCommentId={setAuthMessageCommentId} + authMessageCommentId={authMessageCommentId} + replyToCommentId={replyToCommentId} // Передаем ID для ответа + setReplyToCommentId={setReplyToCommentId} // Передаем функцию для сброса ID + setShowNewCommentForm={setShowNewCommentForm} + /> + ))} +
+ )} + + + {/* + + ) : ( + isAuthenticated && ( + + ) + )} + + {!isAuthenticated && !authMessageCommentId && ( +
+ Войдите, чтобы иметь возможность комментировать +
+ )} + + + ); +} + +// CommentList.propTypes = { +// article: PropTypes.shape({ +// _id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), +// description: PropTypes.string, +// madeIn: PropTypes.object, +// category: PropTypes.object, +// edition: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), +// price: PropTypes.number, +// }).isRequired, +// onAdd: PropTypes.func, +// t: PropTypes.func, +// }; + +export default memo(CommentList); diff --git a/src/components/comment-list/style.css b/src/components/comment-list/style.css new file mode 100644 index 000000000..9cc63d57b --- /dev/null +++ b/src/components/comment-list/style.css @@ -0,0 +1,64 @@ +.CommentCard { + +} + +.CommentList-comments { + font-family: var(--second-font-family); + font-size: 24px; + font-weight: 700; + line-height: 32px; + margin-top: 16px; + } + +.CommentCard-info { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 6px; + font-size: 12px; + line-height: 18px; +} + +.CommentCard-username { + font-weight: 700; +} + +.CommentCard-date { + color: var(--date-text); +} + +.CommentCard-comment { + font-size: 14px; + line-height: 20px; +} + +.CommentCard-answer { + font-size: 12px; + line-height: 18px; + color: var(--primary); + font-weight: 700; + margin-bottom: 6px; +} + +.CommentCard-replies { + /* background-color: aquamarine; */ + /* margin-left: 100px; */ + margin-left: 40px; + margin-bottom: 16px; +} + +.CommentCard-authcaution { + margin: 24px 0 24px 0; +} + +/* CommentCard-authcaution a { + color: var(--primary) !important; +} */ + +/* CommentCard-authlink { + color: var(--primary) !important; +} */ + +.CommentList-authcaution { + margin: 24px 0 24px 0; +} \ No newline at end of file diff --git a/src/config.js b/src/config.js index 67c72f734..1e0668b40 100644 --- a/src/config.js +++ b/src/config.js @@ -1,4 +1,5 @@ const isProduction = process.env.NODE_ENV === 'production'; +import * as translations from './i18n/translations'; /** * Настройки сервисов @@ -18,6 +19,13 @@ const config = { api: { baseUrl: '', }, + i18n: { + locale: 'ru', + translations: { + ru: translations['ru'], + en: translations['en'], + } + } }; export default config; diff --git a/src/containers/locale-select/index.js b/src/containers/locale-select/index.js index a60df2a9a..f48b29c02 100644 --- a/src/containers/locale-select/index.js +++ b/src/containers/locale-select/index.js @@ -1,11 +1,23 @@ -import { memo, useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo, useState } from 'react'; import useStore from '../../hooks/use-store'; import useSelector from '../../hooks/use-selector'; import useTranslate from '../../hooks/use-translate'; import Select from '../../components/select'; function LocaleSelect() { - const { lang, setLang } = useTranslate(); + // const { lang, setLang } = useTranslate(); + // console.log('Что показываешь в useTranslate', useTranslate()); + // const [lang, setLang] = useState('ru'); + const { locale, setLocale } = useTranslate(); + // const { locale, t } = useTranslate(); + console.log('LocaleSelect locale', locale); + + // const handleChange = (lang) => { + // changeLocale(lang); + // setLang(lang); + // }; + + const options = { lang: useMemo( @@ -17,7 +29,8 @@ function LocaleSelect() { ), }; - return ; + return + + +)} + + return (
@@ -42,36 +68,41 @@ function CommentCard(props) {
- {comment.text} + {comment._id}
+ {comment.text}
Ответить
{/* Сообщение о необходимости авторизации */} - {showAuthMessage && ( + {/* {showAuthMessage && (
Войдите, чтобы иметь возможность комментировать
- )} + )} */} {/* Форма для ответа на комментарий */} {/* {showReplyForm && ( */} - {replyToCommentId === comment._id && ( + {/* {isAuthenticated && replyToCommentId === comment._id && ( */} + {/* Форма для ответа на комментарий, рендерится после всех дочерних комментариев на одном уровне */} + + {isAuthenticated && (replyToCommentId === comment._id) && (
{ e.preventDefault(); - const replyText = e.target.elements.reply.value; // Получаем текст из поля ввода + const replyText = e.target.elements.reply.value; handleSubmitReply(replyText); }}> - +
- )} + )} + {/* Рендерим детей только если глубина меньше MAX_DEPTH */} {comment.children && comment.children.length > 0 && (
- {comment.children.map(child => ( + {comment.children.map((child, index) => ( )} - - - {/*
+ + {/* Форма для ответа на комментарий после последнего дочернего элемента */} + {isAuthenticated && showReplyForm && ( +
{ + e.preventDefault(); + const replyText = e.target.elements.reply.value; + handleSubmitReply(replyText); + }}> + + +
+ )} + ); -} +} + // CommentCard.propTypes = { // article: PropTypes.shape({ diff --git a/src/components/comment-list/index.js b/src/components/comment-list/index.js index a824a8c55..878b37422 100644 --- a/src/components/comment-list/index.js +++ b/src/components/comment-list/index.js @@ -1,11 +1,13 @@ -import { memo, useState } from 'react'; +import { memo, useState, useMemo, useCallback } from 'react'; // import PropTypes from 'prop-types'; import { cn as bem } from '@bem-react/classname'; // import numberFormat from '../../utils/number-format'; // import Button from '../button'; import './style.css'; // import dateFormat from '../../utils/date-format'; - +import { useDispatch } from 'react-redux'; +import commentsActions from '../../store-redux/comments/actions'; +import { useParams } from 'react-router-dom'; import CommentCard from '../comment-card'; import useSelector from '../../hooks/use-selector'; import { Link } from 'react-router-dom'; @@ -17,13 +19,54 @@ function CommentList(props) { const [authMessageCommentId, setAuthMessageCommentId] = useState(null); const [showNewCommentForm, setShowNewCommentForm] = useState(true); const [replyToCommentId, setReplyToCommentId] = useState(null); + const params = useParams(); + const [newComment, setNewComment] = useState({ + parent: { + _id: '', + _type: '', + }, + text: '', + }); + + const dispatch = useDispatch(); const select = useSelector(state => ({ exists: state.session.exists, })); + + const parent = useMemo( + () => ({ + _id: replyToCommentId || params.id, + _type: replyToCommentId ? 'comment' : 'article', + }), + [replyToCommentId, params.id], + ); + + const callbacks = { + onChange: useCallback( + e => { + setNewComment({ + parent, + text: e.target.value, + }); + }, + [setNewComment, replyToCommentId], + ), + + onSubmit: useCallback( + async e => { + // e.preventDefault(); + if (newComment) { + // e.preventDefault(); + await dispatch(commentsActions.create(newComment)); + dispatch(commentsActions.load(params.id)); + } + }, [newComment], + ) + }; - const isAuthenticated = false; - // const isAuthenticated = select.exists; + // const isAuthenticated = false; + const isAuthenticated = select.exists; const handleAddComment = (parentId, commentText) => { if (!isAuthenticated) { @@ -31,6 +74,7 @@ function CommentList(props) { } else { // @todo написать логику добавления комментария console.log(parentId, commentText); + callbacks.onSubmit(parentId, commentText); } setReplyToCommentId(null); // Сбросить ID после добавления комментария } @@ -65,6 +109,7 @@ function CommentList(props) { replyToCommentId={replyToCommentId} // Передаем ID для ответа setReplyToCommentId={setReplyToCommentId} // Передаем функцию для сброса ID setShowNewCommentForm={setShowNewCommentForm} + onChangae={callbacks.onChange} /> ) : (

Комментарии недоступны

diff --git a/src/store-redux/comments/actions.js b/src/store-redux/comments/actions.js index 97e26e904..41a18a2ae 100644 --- a/src/store-redux/comments/actions.js +++ b/src/store-redux/comments/actions.js @@ -21,4 +21,22 @@ export default { } } }, + + create: (id, comment) => { + return async (dispatch, getState, services) => { + dispatch({ type: 'comments/create-start' }); + + try { + const res = await services.api.request({ + url: '/api/v1/comments', + method: 'POST', + body: JSON.stringify({ _id: id, text: comment, parent: {} }), + }); + console.log('Ваше сообщение отправляется', { id, comment }); + dispatch({ type: 'comments/create-success', payload: { data: res.data.result.items } }); + } catch (e) { + dispatch({ type: 'comments/create-error' }); + } + } + } }; diff --git a/src/store-redux/comments/reducer.js b/src/store-redux/comments/reducer.js index 0b84641ab..6e56acdae 100644 --- a/src/store-redux/comments/reducer.js +++ b/src/store-redux/comments/reducer.js @@ -17,6 +17,15 @@ function reducer(state = initialState, action) { case 'comments/load-error': return { ...state, data: {}, count: {}, waiting: false }; //@todo текст ошибки сохранять? + case 'comments/create-start': + return { ...state, waiting: true, success: true }; + + case 'comments/create-success': + return { ...state, waiting: false, success: true } + + case 'comments/create-error': + return { ...state, waiting: false, success: false }; + default: // Нет изменений return state; From 8771c0a2d4a75b83d9cc9000ee06c5190fccbb4b Mon Sep 17 00:00:00 2001 From: Sergei Iu Date: Sun, 20 Apr 2025 11:16:31 +0300 Subject: [PATCH 03/15] fix: submitting comments; add styles for a new comment form --- src/components/comment-card/index.js | 30 ++++++++++++++------------- src/components/comment-list/index.js | 25 +++++++++++++--------- src/components/comment-list/style.css | 22 ++++++++++++++++++++ src/store-redux/comments/actions.js | 6 +++--- src/store-redux/comments/reducer.js | 1 + 5 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/components/comment-card/index.js b/src/components/comment-card/index.js index 9261750d7..87a0261f3 100644 --- a/src/components/comment-card/index.js +++ b/src/components/comment-card/index.js @@ -1,26 +1,28 @@ import { memo, useState } from 'react'; import PropTypes from 'prop-types'; import { cn as bem } from '@bem-react/classname'; -import numberFormat from '../../utils/number-format'; -import Button from '../button'; +// import numberFormat from '../../utils/number-format'; +// import Button from '../button'; import './style.css'; import dateFormat from '../../utils/date-format'; import { Link } from 'react-router-dom'; function CommentCard(props) { - const [showReplyForm, setShowReplyForm] = useState(false); - const { comment, isAuthenticated, showAuthMessage, onReplyClick, handleAddComment, setAuthMessageCommentId, authMessageCommentId, replyToCommentId, setReplyToCommentId, setShowNewCommentForm, onChange } = props; + // const [showReplyForm, setShowReplyForm] = useState(false); + const { comment, isAuthenticated, onReplyClick, handleAddComment, + setAuthMessageCommentId, authMessageCommentId, + replyToCommentId, setReplyToCommentId, + setShowNewCommentForm, onChange } = props; const cn = bem('CommentCard'); - // console.log('Comments from CommentCard', comment); + const [showReplyForm, setShowReplyForm] = useState(false); + const handleReplyClick = () => { if (!isAuthenticated) { - setAuthMessageCommentId(comment._id); // Устанавливаем идентификатор текущего комментария + setAuthMessageCommentId(comment._id); } else { - // onReplyClick(); // Вызываем функцию из родителя для обработки ответа - // setShowReplyForm(!showReplyForm); // Переключаем видимость формы ответа - setReplyToCommentId(replyToCommentId === comment._id ? null : comment._id); // Переключаем ответ на текущий комментарий - setShowNewCommentForm(false); // Скрываем форму добавления нового комментария + setReplyToCommentId(replyToCommentId === comment._id ? null : comment._id); + setShowNewCommentForm(false); console.log('replyToCommentId', replyToCommentId); } }; @@ -75,12 +77,11 @@ function CommentCard(props) { Ответить - {/* Сообщение о необходимости авторизации */} - {/* {showAuthMessage && ( + {!isAuthenticated && (authMessageCommentId === comment._id) && (
Войдите, чтобы иметь возможность комментировать
- )} */} + )} {/* Форма для ответа на комментарий */} {/* {showReplyForm && ( */} @@ -108,13 +109,14 @@ function CommentCard(props) { comment={child} handleAddComment={handleAddComment} isAuthenticated={isAuthenticated} - showAuthMessage={authMessageCommentId === child._id} + // showAuthMessage={authMessageCommentId === child._id} onReplyClick={() => handleReplyClick(child._id)} setAuthMessageCommentId={setAuthMessageCommentId} authMessageCommentId={authMessageCommentId} replyToCommentId={replyToCommentId} // Передаем ID для ответа setReplyToCommentId={setReplyToCommentId} // Передаем функцию для сброса ID setShowNewCommentForm={setShowNewCommentForm} + onChange={onChange} /> ))} diff --git a/src/components/comment-list/index.js b/src/components/comment-list/index.js index 878b37422..3c03ec19a 100644 --- a/src/components/comment-list/index.js +++ b/src/components/comment-list/index.js @@ -11,14 +11,17 @@ import { useParams } from 'react-router-dom'; import CommentCard from '../comment-card'; import useSelector from '../../hooks/use-selector'; import { Link } from 'react-router-dom'; +import Button from '../button'; function CommentList(props) { const { comments, commentCount, t = text => text } = props; - - const [showAuthMessage, setShowAuthMessage] = useState(false); + // Устанавливаем идентификатор текущего комментария const [authMessageCommentId, setAuthMessageCommentId] = useState(null); + // Скрываем форму добавления нового комментария const [showNewCommentForm, setShowNewCommentForm] = useState(true); + // Передаем ID для ответа const [replyToCommentId, setReplyToCommentId] = useState(null); + const params = useParams(); const [newComment, setNewComment] = useState({ parent: { @@ -65,7 +68,6 @@ function CommentList(props) { ) }; - // const isAuthenticated = false; const isAuthenticated = select.exists; const handleAddComment = (parentId, commentText) => { @@ -102,14 +104,15 @@ function CommentList(props) { comment={comment} handleAddComment={handleAddComment} isAuthenticated={isAuthenticated} - showAuthMessage={authMessageCommentId === comment._id} onReplyClick={() => handleReplyClick(comment._id)} + setAuthMessageCommentId={setAuthMessageCommentId} authMessageCommentId={authMessageCommentId} - replyToCommentId={replyToCommentId} // Передаем ID для ответа - setReplyToCommentId={setReplyToCommentId} // Передаем функцию для сброса ID + replyToCommentId={replyToCommentId} + setReplyToCommentId={setReplyToCommentId} + setShowNewCommentForm={setShowNewCommentForm} - onChangae={callbacks.onChange} + onChange={callbacks.onChange} /> ) : (

Комментарии недоступны

@@ -118,15 +121,17 @@ function CommentList(props) { {/* Форма для нового комментария */} {isAuthenticated && showNewCommentForm ? ( - // {isAuthenticated && showNewCommentForm ? (
{ e.preventDefault(); const newCommentText = e.target.elements.newComment.value; handleAddComment(null, newCommentText); // Передаем null как parentId для нового комментария setShowNewCommentForm(false); // Скрываем форму после отправки }}> - - +

Новый комментарий

+